Java多线程wait/notify原理
一、引入wait/notify
回顾:在之前的学习中,当我们创建一个对象后,synchronized给对象上锁,JVM会给对象头关联一个Monitor对象,这个Monitor由三部分组成。
一是Owner对象,里面存储的是创建该对象的线程
二是EntryList,想试图获取该对象资源的其它堵塞线程队列
三是WaitSet,存储的是放弃对象锁的线程
- Owner线程中的锁对象,如果发现条件不满足,调用wait()方法,既可以进入到WaitSet变为WAITING状态
- EntryList下和WaitSet下的线程都属于堵塞状态,不占用CPU时间片
- EntryList下的线程会在Owner是释放锁时被唤醒
- WaitSet下的线程会在Owner线程调用notify或notifyAll时被唤醒,但是唤醒后并不意味立刻获得锁,需要进入EntryList重新竞争锁
API介绍
wait()
会让obj对象由拥有锁到暂时放弃锁,进入到waitset中notify()
会唤醒在waitset中的线程,然后需要进入EntryList重新竞争锁notifyAll()
会唤醒所有在waitset中的线程,然后需要进入EntryList重新竞争锁
wait(long n)
和wait()
的区别是,前者是有时限的等待,时间到后自己进入EntryList中,后者如果没有notify()
或notifyAll()
唤醒则进入永久等待。
wait/notify 的正确姿势
开始之前先看看sleep()
和wait()
方法有什么区别?
sleep()
是Thread的静态方法,而wait()方法是Object类下的方法sleep()
方法不需要与synchronized配合使用,但是wait()
方法必须和sysnchronized配合才能使用- 调用
sleep()
方法,对象不会释放锁,调用wait()方法时对象会释放锁 - 相同点就是调用两个方法后,线程的状态都是TIMED_WAITING状态
wait/notify与锁关系的探究
wait()时是否需要持有锁? notify()是否需要持有锁?先说答案:都需要持有锁。
wait需要持有锁的原因是,你肯定需要知道在哪个对象上进行等待,如果不持有锁,将无法做到对象变更时进行实时感知通知的作用。与此同时,为了让其他线程可以操作该值的变化,它必须要先释放掉锁,然后在该节点上进行等待。不持有锁而进行wait,可能会导致长眠不起。而且,如果不持有锁,则当wait之后的操作,都可能是错的,因为可能这个数据已经过时,其实也叫线程不安全了。总之,一切为了安全,单独的wait做不成这事。
notify需要持有锁的原因是,它要保证线程的安全,只有它知道数据变化了,所以它有权力去通知其他线程数据变化。而且通知完之后,不能立即释放锁,即必须在持有锁的情况下进行通知,否则notify后续的工作的线程安全性将无法保证,尽量它是在lock的范围内,但却因为锁释放,将导致不可预期的结果。而且在notify的时候,并不能真正地将对应的线程唤醒,即不能从操作系统层面唤醒线程,因为此时当前通知线程持有锁,而此时如果将其他等待线程唤醒,它们将立即参与到锁的竞争中来,而这时的竞争是一定会失败的,这可能会导致被唤醒的线程立即又进入等待队列,更糟糕的是它可能再也不会被唤醒 了。所以不能将在持有锁的时,将对应的线程真正唤醒,我们看到的notify只是从语言上下文级别,将它从等待队列转移到同步队列而已(就是从上面的WaitSet进入EntryList),对此操作系统一无所知。