题目描述
建立三个线程A、B、C,A线程字母A,B线程字母B,C线程字母C,但是要求三个线程同时运行,并且实现交替顺序打印,即按照ABC ABC ABC的顺序打印。
写在前面
这个题算是个臭名昭著的多线程题了,我是没想出来有啥地方需要这样使用多线程。无论是对现代分布式服务端应用,还是客户端Android应用开发都没有这样使用多线程的。保存中间状态对某些
面试官
来说是什么很困难的事情么。服务端操作系统基本都是非实时系统,从根本原理上讲并发保序性就是不保证的。这种强行反工程学的东西只是一个无脑的八股文。
这题有多种变种,无非是添加打印次数之类的截止点,添加计数器即可解决。
网上有使用
Synchroized
关键字和线程的
wait,notify
方法去解决的,写的实在是太复杂了。对现代java(java 8+版本),没有任何理由使用
Synchroized
,
wait,notify
这些Java 1.0时代的老古董,难以理解又使用不便。
Java 5后Java新增了李大神的并发工具包
java.util.concurrent
,里面有了可重入锁
ReentrantLock
,信号量
Semaphore
等,通过这些工具可以实现三线程交替打印,但是代码复杂度都不低。
实现三个线程交替顺序,核心思路无非是
控制并发度和根据状态转移
。控制并发度,同一时刻只能有一个线程运行,否则多线程并发,系统不保证顺序,就会乱打,或者一个线程打印多次。根据状态转移,也就是根据状态切换到一个状态的线程,必须记录当前状态,然后根据当前状态进行转移,才能实现顺序打印。
使用
ReentrantLock
锁的方案
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