并发编程——多个线程交替打印

有一个非常著名的多线程交互的场景:

有3个线程,让这三个线程交替打印abc abc,一共打印4次

这个场景要怎么实现呢?
首先,我们需要有3个线程,t1t2t3这三个线程分别负责打印a,b,c,所以,我们需要一个负责打印的方法。但是 当t1打印完a之后,不能马上再次打印a,必须等t2,t3打印完毕后,才能继续打印,也就是说,这个打印的方法需要有个变量控制当前能不能进行打印,如果能打印,则打印,如果不能打印,就等待。
printer的代码如下

static class Printer{
	private int current = 1;
	private final Object lockObj = new Object();
	Logger logger = LoggerFactory.getLogger(this.getClass());
	public void print(String str,int signal,int nextSignal){
		for (int i = 0; i < 4; i++) {
			synchronized (lockObj){
				while (current!=signal){
					try {
						lockObj.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				logger.info(str);
				current = nextSignal;
				lockObj.notifyAll();
			}
		}
	}
	}

线程调用printer:

Printer printer = new Printer();
CountDownLatch countDownLatch = new CountDownLatch(1);
Thread t1 = new Thread(() -> {
	try {
		countDownLatch.await();
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	printer.print("a", 1, 2);
});
Thread t2 = new Thread(() -> {
	try {
		countDownLatch.await();
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	printer.print("b", 2, 3);
});
Thread t3 = new Thread(() -> {
	try {
		countDownLatch.await();
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	printer.print("c", 3, 1);
});
t1.start();
t2.start();
t3.start();
countDownLatch.countDown();

因为必须是abc,按照这个顺序打印,所以t1必须是第一个执行的,也就是说printer中默认的标志位得是t1的标志位(1)。因为这三个线程是同时启动的,假如t3先或得CPU,这时,t3通过synchronized获取到锁,然后判断,当前t3是否可以打印,如果t3不能打印current!=signal,那么就等待,阻塞当前线程。假如,这时,t1或得了CPU,并且满足了打印的条件,那么t1开始打印,打印完毕后,将标志位标记为t2(2),同时,唤醒所有等待的线程。这时候,t3也会被唤醒,被唤醒的线程会从wait之后继续执行,但是,t3可能还不满足打印条件,所以,这里是while (current!=signal)
并发编程——多个线程交替打印
程序的执行结果如下:
并发编程——多个线程交替打印
同时,这个printer还有一个ReentrantLock版本的,大体意思一样,只是使用了ReentrantLock

static class Printer{
	private int current = 1;
	private final ReentrantLock lock = new ReentrantLock();
	private final Condition condition = lock.newCondition();
	Logger logger = LoggerFactory.getLogger(this.getClass());
	public void print(String str,int signal,int nextSignal){
		for (int i = 0; i < 4; i++) {
			try {
				lock.lock();
				while (current!=signal){
					try {
						condition.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				logger.info(str);
				current = nextSignal;
				condition.signalAll();
			}finally {
				lock.unlock();
			}
		}
	}
}

使用ReentrantLock时,一定要记得try finally,在finally中释放锁。

总结:
1、 但凡synchronized+wait+notify,一定会用到while,因为notify(随机唤醒)/notifyALL(唤醒所有)之后,可能唤醒的并不是真的可以执行的线程,需要再次判断是否满足继续执行的条件。所以就得用到while
2、对这个问题的理解。之前对这个问题的理解或者说对多线程的理解不到位,总是觉得无从下手。尤其是不理解printerfor到底循环的是什么,一直以为循环的是abc abc,但是,其实对于t1来说,这个for循环的结果是aaa,对于t2来说,这个for循环的结果是bbb。多线程的问题还是得站在线程的角度看,才能真正理解

上一篇:Applying white space in UI design


下一篇:pwsh7 tab键下拉菜单智能提示