这个东西是我今天看多线程通信的时候无意中想到的,为什么像wait()、notify()、notifyAll()之类的线程间通信需要放在同步块种,换言之为什么要用synchronized。jie如果wait()方法不在同步块中,会怎么样嘞:
@Test public void test() { try { new Object().wait(); } catch (InterruptedException e) { e.printStackTrace(); } } 复制代码
结果是:
经过一番谷歌,参照了各路大神的博客,终于找到了答案。
首先我们来举个例子,一个消费者线程、一个生产者线程。生产者的任务为count+1,然后唤醒消费者;消费者的任务为count-1,等到count为0时陷入沉睡。
生产者伪代码:
count++; notify(); 复制代码
消费者伪代码:
while(count <= 0){ wait(); count--; } 复制代码
熟悉多线程的朋友应该一眼就看出来了问题,如果生产者和消费者的步骤混杂在一起会发生什么。
首先我们先假设count = 0,这个时候消费者检查count的值,发现count <= 0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个通知就会被丢掉。紧接着,消费者就睡过去了……
图片来自为什么wait()会这样
这就是所谓的Lost Wake-Up Problem
现在我们应该能发现问题的根源在于,消费者在检查count到调用wait()之间,count就可能被改掉了。
那我们如何解决呢?
让消费者和生产者竞争一把锁,竞争到了的,才能够修改count的值。
于是乎生产者代码:
lock(); count++; notify(); unlock(); 复制代码
消费者代码:
lock(); while(count <= 0){ wait(); count--; } unlock(); 复制代码
现在我们来看看,这样子真的解决了吗?
答案是毫无卵用,依旧会出现lost wake up问题,而且和无锁的表现是一样的。
因为wait()是释放锁然后等待获取锁,当然要先获得锁才行,但在这边连锁都莫得。
所以,我们可以总结到,为了避免出现这种 lost wake up 问题,在这种模型之下,总应该将我们的代码放进去的同步块中。
Java强制我们的 wait()/notify() 调用必须要在一个同步块中,就是不想让我们在不经意间出现这种 lost wake up 问题。
不仅仅是这两个方法,包括 java.util.concurrent.locks.Condition 的 await()/signal() 也必须要在同步块中。
正解:
private Object obj = new Object(); private Object anotherObj = new Object(); @Test public void produce() { synchronized (obj) { try { //同步块要对当前线程负责,而不是anotherObj.notify(); obj.notify(); } catch (Exception e) { e.printStackTrace(); } } } 复制代码
wait()、notify()和notifyAll()
wait()、notify() 和 notifyAll()方法是本地方法,并且为 final 方法,无法被重写。
调用某个对象的 wait() 方法能让当前线程阻塞,并且当前线程必须拥有此对象的 monitor(即锁,或者叫管程)。
调用某个对象的 notify() 方法能够唤醒一个正在等待这个对象的 monitor 的线程,如果有多个线程都在等待这个对象的 monitor,则只能唤醒其中一个线程。
调用 notifyAll() 方法能够唤醒所有正在等待这个对象的monitor的线程。
/** * wait() && notify()方法 * 这两个方法是在Object中定义的,用于协调线程同步,比 join 更加灵活 */ public class NotifyDemo { public static void main(String[] args) { //写两个线程 1.图片下载 Object obj=new Object(); Thread download=new Thread(){ public void run() { System.out.println("开始下载图片"); for (int i = 0; i < 101; i+=10) { System.out.println("down"+i+"%"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("图片下载成功"); synchronized (obj) { obj.notify();//唤起 } System.out.println("开始下载附件"); for (int i = 0; i < 101; i+=10) { System.out.println("附件下载"+i+"%"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("附件下载成功"); } }; //2.图片展示 Thread show=new Thread(){ public void run(){ synchronized (obj) { try { obj.wait();//阻塞当前 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("show:开始展示图片"); System.out.println("图片展示完毕"); } } }; download.start(); show.start(); } } 复制代码