为什么 java wait/notify 必须与 synchronized 一起使用,jvm究竟做了些什么
huangriyan
2014-11-24
有关这个话题,网上查了很久,都没有太什么的讨论,各位能不能深入讲解一下
|
|
chainhou
2014-12-24
个人感觉,这样做关键是因为锁。
在进入synchronized的时候获取锁,退出的时候释放锁。在jvm里有相应的指令对应。 而如果没有synchronized,直接使用wait/notity时,无法确认哪个锁。比如我手里有一相苹果,我会说我把这个苹果送人了,这个时候别人需要才能拿到。如果我没有这个苹果(锁),我只是口口声声的说我要让出这个苹果,但却没有。 |
|
Now7!
2014-12-25
我们假设wait()/notify()都不需要锁。现在场景是,某条件没满足,导致我们需要block等待一下,直到条件满足为止。我们用obj来表达在等待的条件队列。
最初的尝试 我们在obj上wait(),期待其醒过来时条件是满足的。 obj.wait(); // 假设这样OK [问题1] obj.wait()需要条件检查 不过上述代码是不可行的,因为obj.wait()会莫名其妙醒来,即条件不满足时也会醒过来。这不是我们期望的结果;所以我们需要持续检查条件,直到满足时才doSomething(): while(!条件满足) // line 1 { obj.wait(); // line 3 } doSomething(); [问题2] obj.wait(), obj.notify()都需要原子性(互斥性) 假设上述代码能够工作并且block了线程T1,那么另外的线程T2可以notify()、并且更改条件,使得T1在line 1那里满足条件,从而不再wait(): 更改条件为满足; // line 1 obj.notify(); // line 2 这段代码的问题在于,T1和T2的并行执行序列可能如下: T1 line1 // T1检查条件不满足 T2 line1 // T2更改条件为满足 T2 line2 // T2.notify() T1 line3 // T1.wait() 这将导致nofity丢失,即T2 notify()时还没有任何的wait()线程,而T1一段时间后真正wait()了却不可能收到notify()了。 所以T1的条件检查和wait()需要原子性(互斥性): synchronized(mutex) { while(!条件满足) { obj.wait(); } doSomething(); } 且T2尊重这个原子性(互斥性) synchronized(mutex) { 更改条件为满足; obj.notify(); } 这样,T1的条件检查+wait()与T2的设置条件+notify()互斥了,从而notify就不会丢失了。 [问题3] wait()需要释放mutex 其实到目前为止,还是隐含着一个问题。因为这俩线程的一种执行可能是,先T1 wait(),再T2 notify();而问题在于,T1持有mutex后block住了,T2一直无法获得mutex,从而永无可能notify()并将T1的block状态解除,就与T1形成了死锁。所以JVM在实现wait()方法时,一定需要先隐式的释放mutex,再block,并且被notify()后从wait()方法返回前,隐式的重新获得了mutex后才能继续user code的执行。要做到这点,就需要提供mutex引用给obj.wait()方法,否则obj.wait()不知道该隐形释放哪个mutex: synchronized(mutex) { while(!条件满足) { obj.wait(mutex); // obj.wait(mutex)伪实现 // [1] unlock(mutex) // [2] block住自己,等待notify() // [3] 已被notify(),重新lock(mutex) // [4] obj.wait(mutex)方法成功返回 } doSomething(); } [最终形态] 把mutex和obj合一 其它线程API如PThread提供wait()函数的签名是类似cond_wait(obj, mutex)的,因为同一个mutex可以管多个obj条件队列。而Java内置的锁与条件队列的关系是1:1,所以就直接把obj当成mutex来用了。因此此处就不需要额外提供mutex,而直接使用obj即可,代码也更简洁: synchronized(obj) { while(!条件满足) { obj.wait(); } doSomething(); } 多讨论一下sleep()和interrupt() Hotspot里的[1]synchronized的左括号{,[2]Object.wait(),[3]AQS.acquire(),[4]Condition.await(),[5]Thread.sleep()这五者的实现在某种意义上是一致的,即基于Hotspot内部的ParkEvent.park()和Parker.park(),在linux平台上最终基于Pthread的cond_timedwait()/cond_wait();而[6]synchronized的右括号},[7]Object.notify()/notifyAll(),[8]AQS.release(),[9]Condition.signal()/signalAll(),[10]Thread.interrupt()这五者的实现在某种意义上也是一致的,即基于Hotspot内部的ParkEvent.unpark()和Parker.unpark(),在linux平台上最终基于Pthread的cond_signal()。但其它8者都需要先lock再unlock,只有[5]Thread.sleep()和[10]Thread.interrupt()不需要这样,原因是这对组合是不需要检测条件以及更改条件的,所以不需要上面[问题2]讨论的原子性(互斥性)。 结语 以上就是OP提到的wait()/notify()/notifyAll()为什么需要在synchronized(obj)包围里的原因了以及JVM做了些什么,更多是个人理解,不当之处请大家纠正。 |
|
ZHH2009
2014-12-25
这个问题需要讨论吗?大学学操作系统的课程时都会讲临界区
怎么界定临界区,synchronized把一段代码框起来不就像临界区了吗? 进到临界区的线程总会因为某种条件未满足需要睡一下觉,或等别的线程完成, 有等就有通知,多么自然的逻辑啊。 |
|
skyknowsme
2015-03-10
Java的Synchronization是Tony Hoare提出的Monitor的一个实现。
具体可以参照这篇wiki。 http://en.wikipedia.org/wiki/Monitor_(synchronization) synchronization可以看做是已经实现了lock/unlock的基本功能, 用于互斥访问临界区,而wait和notify,notifyAll是高级一点的同步机制。 你可以这样去理解, 简单的互斥访问临界区,用synchronization足够了, 用于实现生产者消费者的例子,你会发现wait/notify很好使,直觉上就是你可以等直到你要的条件满足。 wait就会有一个wait list,里面是等待的线程, wait就是将自己放入list,notify就是通知list中的某个去执行。 显然,这个wait list必须得互斥访问,所以是在临界区里的, 所以它需要一个synchronization。 |
相关讨论
相关资源推荐
- 为什么 wait/notify 必须与 synchronized 一起使用?
- 为什么wait/notify必须要和synchronized一起使用?
- 为什么 java wait/notify 必须与 synchronized 一起使用
- 为什么wait和notify必须放在synchronized中?
- 为什么wait/notify必须要强制要求放在synchronized中
- java Condition类与wait/notify的比较
- 并发编程的艺术(4):浅谈wait/notify、park/unpark的使用和原理
- JAVA多线程基础篇-线程通信(wait/notify)
- Java多线程wait/notify原理
- Java多线程(2)---synchronized、wait、notify