简介
wait ,notify,notifyAll和原先学习的方法不同,这些是Object类中的方法,为什么是Object类中的方法,而不定义成线程中的方法。
当时面试的时候,面试官问了个这问题。网上找一下标准答案。
wait与notify,notifyAll是不仅是普通方法与同步机制,还是java两个线程中的通信机制。因为该通信机制与synchronized绑定,又需要保证对所有对象可用,所以放到Object中比较好。
每个对象都可以上锁,这也就是放到Object中,而不是放到Thread类中的原因。
在 Java 中,为了进入代码的临界区,线程需要锁定并等待锁,他们不知道哪些线程持有锁,而只是知道锁被某个线程持有, 并且需要等待以取得锁, 而不是去了解哪个线程在同步块内,并请求它们释放锁。
其实我觉得上面说的不太好,但让我总结个标准答案我也组织不好语言。反正在我的理解,wait,notify,notifyAll这些方法,是为了与synchronized配合使用,保证程序之间的指令执行次序,不会造成数据问题。要知道,线程进入临界区后,依赖的是锁资源主观层面来完成线程之间的阻塞,而不是在线程主观层面请求释放锁。锁是对象层面的东西,所以放到Object中。
总感觉不对劲,这什么sb问题。不想那么多,往下写。
底层原理
wait,notify,notifyAll都是结合synchronized来进行使用的,而wait状态的数据是存放到monitor中的waitSet中,所以调用这东西就直接重量级锁了。
流程如下:
1.当一个线程执行到某个阶段,不满足执行条件,调用wait方法,将当前线程放到waitSet中,成为waiting或者timed_waiting状态,并将Owner置为空,释放掉持有的锁资源。
2.从EntryList中唤醒线程,参与CPU时间片的竞争
3.当方法执行到当前锁资源的notify或notifyall方法,此时wait状态线程苏醒,放入至entryList中进行竞争,竞争成功后,线程继续执行,否则激素在EntryList中阻塞。
看一下代码吧。jiu就
package com.bo.threadstudy.four;
import lombok.extern.slf4j.Slf4j;
/**
* 测试线程wait以及notify,内存通信
*/
@Slf4j
public class WaitNotifyTest {
private static final Object lock = new Object();
//先写个普通的方法吧,不考虑虚拟唤醒的情况
public static void main(String[] args) {
//没什么意思,初始简单版
new Thread(() -> {
log.debug("线程1在执行");
synchronized (lock){
lock.notify();
// lock.notifyAll();
}
},"t1").start();
new Thread(() -> {
synchronized (lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("线程2在执行");
},"t2").start();
new Thread(() -> {
synchronized (lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("线程3在执行");
},"t3").start();
}
}
很简单的一个唤醒机制,我刚才说了,notify或notifyAll方法会先将waiting或者timed_waiting状态的线程存放至entryList中,也就是blocking状态,看看是不是?
再往下执行一步
这里的monitor代表的就是阻塞,它成功进入了阻塞状态。
wait方法是将线程存放至waitSet中,而notify是随机唤醒一个线程进入entryList中进行等待,notifyAll是唤醒当前锁全部wait的线程。
notify的结果,有一个线程的结果阻塞住,出不来。
notifyAll的结果,两个wait的线程就全部唤醒了,将notify注释,放开notifyAll。
虚假唤醒问题
事实上,我现在有这么个场景,有个辅导老师,需要俩小伙A,B中某个人来填一个表格。模拟一下场景。
主线程作为老师,先搬一个凳子,然后开始叫人
小A和小B默认是等待状态,等待老师唤醒
什么是虚假唤醒,老师想唤醒B,但却不确定用notify具体会唤醒谁。所以用notifyAll?
好吧,notifyAll俩人都唤醒了,但是只需要一个人填表,这个时候,需要加个判定条件,老师意愿到底唤醒谁,另一个随机唤醒的只能回去阻塞。
代码如下:
package com.bo.threadstudy.four;
import lombok.extern.slf4j.Slf4j;
/**
* 写一个例子来做虚假唤醒问题
*/
@Slf4j
public class WaitNotifyExampleTest {
private static boolean aStatus = false;
private static boolean bStatus = false;
public static void main(String[] args) throws InterruptedException {
log.debug("开始准备凳子");
Thread.sleep(100);
Object lock = new Object();
log.debug("准备好了");
new Thread(() -> {
synchronized (lock){
try {
while(!aStatus){
log.debug("小A正在等待");
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("小A在填写表格");
log.debug("填写表格完成");
}
},"小A").start();
new Thread(() -> {
synchronized (lock){
try {
while(!bStatus){
log.debug("小B正在等待");
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("小B在填写表格");
log.debug("填写表格完成");
}
},"小B").start();
synchronized (lock){
bStatus = true;
lock.notifyAll();
}
}
}
如果不想阻塞的话,也可以采用中断操作interrute,来中断另一个等待线程,或者设置一个状态,在一个状态中设置另一个状态,让程序结束。
没事,后面写个练习试试。好好磨练一下。