Thread源码分析之join方法引申--wait 为何一定要写在同步代码块中

别说谁变了你拦得住时间么 提交于 2019-12-05 15:09:17

对于wait的使用,JDK中给出的解释如下

In other words,
* waits should always occur in loops, like this one:
* <pre>
*     synchronized (obj) {
*         while (<condition does not hold>)
*             obj.wait(timeout);
*         ... // Perform action appropriate to condition
*     }
* </pre>
* (For more information on this topic, see Section 3.2.3 in Doug Lea's
* "Concurrent Programming in Java (Second Edition)" (Addison-Wesley,
* 2000), or Item 50 in Joshua Bloch's "Effective Java Programming
* Language Guide" (Addison-Wesley, 2001).

就是说wait使用的套路如下

     synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     }

wait使用的套路总结

  1. 必须在 synchronized 同步方法或者 synchronized 同步块中
  2. 必须在while循环中使用

为什么wait必须在 synchronized 同步方法或者 synchronized 同步块中?

反证举例:对象o的wait(notify、notifyAll)如果不在synchronized 同步方法或者 synchronized 同步块,那么线程I调用o.wait()时,线程II可以(同一时间内)随时调用o.notify(),如下图

线程I处于o.wait()调用后的wait set中,此时代码运行的指针暂存了起来。

因为没有互斥同步,线程II可以随时调用o.notify(),那么不管线程I中的condition条件是否满足,都会发生【①通知继续】。

随着【①通知继续】的发生,线程II认为:我已经通知过线程I,让线程I继续运行了,我的任务完成了!

线程I中接收到【①通知继续】,然后会从暂存的位置取出代码运行指针,继续往o.wait()后面运行,然后又跑到while(condition)这里。刚才说了,condition条件可能还不符合,那么就又进入了o.wait(),暂存起来运行指针,线程I进入对象o的wait set区。

现在是什么情况?线程II认为:我已经通知过线程I,让线程I继续运行了,我的任务完成了!所以线程II不会再次通知了。

线程I呢?如上所述再次进入了对象o的wait set,因为线程II不会再次notify线程I,那么线程I将永远存在于wait set区域,再也出不来了!

所以为啥要同步o.wait()和o.notify()?就是为了互斥执行,只有condition满足时,才让线程II通知线程I从wait set区跳出来继续执行,进而继续执行后续的代码。

为什么wait必须在while循环中使用?

如果wait方法不在while循环中使用,那么通知wait结束,没有再次进行条件判断就继续wait后续代码。但是有可能出现condition并不满足的情况,此时代码逻辑就乱了。

★根本原因--wait函数的设计思路

首先wait方法是属于类Object。可以认为任何一个Object对象都有一个内置锁,因为Object类是所有类的祖先类,也就是说所有类的对象都有一个内置锁。想调用wait、notify、notifyAll方法,必须要先获取对象的内置锁。

wait被设计时就是要和notify(或者notifyAll)配合使用的。

使用场景就是:对象o,其有一个属性为a。属性a被线程I和线程II共享,也就是说a为线程I和线程II的共享变量。当a为true时,线程I调用o的wait。线程II可以改变o中a的状态,改变后调用o的notify方法,通知线程I跳出o的wait方法,继续向后执行代码。

总体来说,人家设计的就是这个套路。

t.join() 是谁拿了谁的锁?

之前说过。实质上调用代码1就相当于调用代码2。分析join的源码,结合wait的使用套路,可以得到这个结论。

假设线程I调用t.join()。t.join()最终调用的是t.wait()。

t.join()方法是synchronized 方法。所以线程I中运行到t.join(),是线程I拿到了对象t的锁。

将t.join()换成代码2,也应该以对象t为锁。所以代码2中,仍然是线程I拿到了对象t的锁。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!