一种简单的三线程交替打印实现(LockSupport实现)
创始人
2025-06-01 09:21:31

题目描述

建立三个线程A、B、C,A线程字母A,B线程字母B,C线程字母C,但是要求三个线程同时运行,并且实现交替顺序打印,即按照ABC ABC ABC的顺序打印。

写在前面

这个题算是个臭名昭著的多线程题了,我是没想出来有啥地方需要这样使用多线程。无论是对现代分布式服务端应用,还是客户端Android应用开发都没有这样使用多线程的。保存中间状态对某些面试官来说是什么很困难的事情么。服务端操作系统基本都是非实时系统,从根本原理上讲并发保序性就是不保证的。这种强行反工程学的东西只是一个无脑的八股文。

这题有多种变种,无非是添加打印次数之类的截止点,添加计数器即可解决。

网上有使用Synchroized关键字和线程的wait,notify方法去解决的,写的实在是太复杂了。对现代java(java 8+版本),没有任何理由使用Synchroizedwait,notify这些Java 1.0时代的老古董,难以理解又使用不便。

Java 5后Java新增了李大神的并发工具包java.util.concurrent,里面有了可重入锁ReentrantLock,信号量Semaphore等,通过这些工具可以实现三线程交替打印,但是代码复杂度都不低。

实现三个线程交替顺序,核心思路无非是控制并发度和根据状态转移。控制并发度,同一时刻只能有一个线程运行,否则多线程并发,系统不保证顺序,就会乱打,或者一个线程打印多次。根据状态转移,也就是根据状态切换到一个状态的线程,必须记录当前状态,然后根据当前状态进行转移,才能实现顺序打印。

使用ReentrantLock锁的方案

基本思路

  • 通过锁可以实现控制并发度
    通过ReentrantLock,我们可以很方便的进行显式的锁操作,即获取锁和释放锁,对于同一个对象锁而言,统一时刻只可能有一个线程拿到了这个锁,此时其他线程通过lock.lock()来获取对象锁时都会被阻塞,直到这个线程通过lock.unlock()操作释放这个锁后,其他线程才能拿到这个锁。

  • 按顺序释放锁可以实现根据状态转移
    实现交替顺序打印的关键就在于,每个线程按顺序释放锁,并唤醒下个线程。此时已知当前线程的状态,释放锁后需要唤醒下个状态的线程。
    但直接释放锁,唤醒哪个线程可以说是随机的。有没有办法唤醒指定线程呢?

    其实唤醒的线程是不随机的,唤醒的是锁等待队列的头线程,但是哪个线程在排队时成为头线程是随机的,可以认为随机唤醒线程。

    通过并发工具包java.util.concurrent提供的,配合ReentrantLock使用的Condition条件唤醒工具。可以实现唤醒指定线程。

ReentrantLock搭配使用的Condition

private Lock lock = new ReentrantLock();  
private Condition condition = lock.newCondition(); 
condition.await();//this.wait();  
condition.signal();//this.notify();  
condition.signalAll();//this.notifyAll();

Condition是被绑定到Lock上的,必须使用lock.newCondition()才能创建一个Condition。从上面的代码可以看出,Synchronized能实现的通信方式,Condition都可以实现,功能类似的代码写在同一行中。但是Condition可以实现,指定Condition去阻塞线程和唤醒线程,而wait,notify是无目标的,所以说当前无任何必要使用wait,notify这类老家伙。

代码实现

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;public class MultiThreadPrint {public void threeThreadAlternatelyPrint(){Lock lock = new ReentrantLock();Condition A = lock.newCondition();Condition B = lock.newCondition();Condition C = lock.newCondition();var a = new Thread(()->{try {while (true) {lock.lock();System.out.print("A");B.signal(); // A执行完唤醒B线程A.await(); // A释放lock锁进入等待}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});var b = new Thread(()->{try {while (true) {lock.lock();System.out.print("B");C.signal();// B执行完唤醒C线程B.await();// B释放lock锁,进入等待}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});var c = new Thread(()->{try {while (true) {lock.lock();System.out.print("C");A.signal();// C执行完唤醒A线程C.await();// C释放lock锁进入等待}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});a.start();b.start();c.start();}
}

可以看到复杂度是着实不低。由于每个线程需要按顺序唤醒下一个线程,所以每个线程体内都包含了加锁解锁,等待唤醒的状态转移过程。

使用线程挂起工具LockSupport

基本思路

最开始已经说了,交替打印核心思路无非是控制并发度和根据状态转移。锁的能力,主要还是控制代码段的竞争,只是暂停线程还有更方便的工具。那就是LockSupport,它可以很方便的挂起线程和唤醒指定线程,一次性满足控制并发度和根据状态转移的要求。

Thread t = new Thread(()->{LockSupport.park(); //可以直接挂起线程
})LockSupport.unpark(t); //可以指定线程唤醒

代码实现


import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.LockSupport;public class MultiThreadPrint {//保存当前状态static volatile String str = "";public void threeThreadAlternatelyPrint(){HashMap tMap = new HashMap();Runnable s = ()-> {//这段线程休眠代码可以不加 不加就是会打印的非常快 //打印太快可能会卡死编辑器或者空台 也可以通过限制打印次数来规避try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//每个线程启动后都直接暂停 等待开始信号 LockSupport.park();str = Thread.currentThread().getName();System.out.print(str);//唤醒下一个线程 线程传递关系保存在tMap里LockSupport.unpark(tMap.get(str));};var a = new Thread(s,"a");var b = new Thread(s,"b");var c = new Thread(s,"c");//tMap保存交替顺序tMap.put(a.getName(),b);tMap.put(b.getName(),c);tMap.put(c.getName(),a);a.start();b.start();c.start();//唤醒a线程 后续就会按顺序打印了LockSupport.unpark(a);}}
}

打印结果

abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc

总结

核心思路是控制并发度和根据状态转移Synchroized关键字和线程的wait,notify早已过时,ReentrantLock太过复杂,用线程挂起工具LockSupport可以较简单的实现。

相关内容

热门资讯

天越冷,越爱吃。后悔没早点买.... 稍微上档次一点的饭店里,有一个食材总会出现,那就是三文鱼。不管是日料店的三文鱼刺身,还是西餐店的香煎...
“私人影院有陪侍服务”,三亚、... (来源:上观新闻)据江苏省广播电视总台荔枝新闻消息,近日,记者在西安、成都、三亚、大理等地探访发现,...
泽连斯基满意 (来源:上观新闻)据央视新闻消息,当地时间12月21日,美国总统特使威特科夫表示,过去三天,乌克兰代...
申科股份聘任41岁张晓非为副总... 12月22日,申科股份公告,聘任张晓非先生为公司副总经理。资料显示,张晓非,男,汉族,1984年6月...
机构岁末布局路线图浮现 202... 转自:经济参考报  临近岁末,机构调研火力全开,调研图谱清晰勾勒出资本对实体经济转型升级的高度关注。...