先看代码
package org.binfa.concurrentprogramming.notify;
import lombok.extern.slf4j.Slf4j;
/**
* notify 唤醒线程
* @Author: jun
* @Date: 2022/7/24 16:25
*/
@Slf4j
public class NotifyTest {
public static void main(String[] args) {
/**
* 知识点:
* 1. notify 会唤醒一个在该共享变量上调用wati()方法后被挂起的线程,具体唤醒哪个线程是随机的
* 2. 被唤醒的线程不能马上从wait方法返回并执行,需要获取到监视器锁才可以继续执行
* 3. 和wait方法一样,notify(),notifyAll()方法的调用需要当前线程获取到监视器锁,否则抛出IllegalMonitorStateException
* 4. 一个需要注意的地方是,在共享变量上调用notifyAll()方法只会唤醒调用这个方法前调用了wait系列函数而被放入共享变量等待集合里面的线程。如果调用notifyAll()方法后一个线程调用了该共享变量的wait)方法而被放入阻塞集合,则该线程是不会被唤醒的。
*/
Object obj = new Object();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
log.info("线程{},开始wait", Thread.currentThread().getName());
obj.wait();
log.info("线程{},wait结束", Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "A");
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
log.info("线程{},开始wait", Thread.currentThread().getName());
obj.wait();
log.info("线程{},wait结束", Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "B");
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
log.info("线程{},开始notifyAll", Thread.currentThread().getName());
obj.notifyAll();
log.info("线程{},notifyAll结束", Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, "C");
threadA.start();
threadB.start();
try {
// todo 这里如果注释,则线程B wait 可能不会结束
Thread.sleep(400);
} catch (Exception e) {
e.printStackTrace();
}
threadC.start();
}
}
返回结果:
[A] 线程A,开始wait
[B] 线程B,开始wait
[C] 线程C,开始notifyAll
[C] 线程C,notifyAll结束
[B] 线程B,wait结束
[A] 线程A,wait结束
将代码通的 TODO sleep()注释后,运行结果
[A] 线程A,开始wait
[C] 线程C,开始notifyAll
[C] 线程C,notifyAll结束
[B] 线程B,开始wait
[A] 线程A,wait结束
总结:在共享变量上调用notifyAll()方法只会唤醒调用这个方法前调用了wait系列函数而被放入共享变量等待集合里面的线程。如果调用notifyAll()方法后一个线程调用了该共享变量的wait()方法而被放入阻塞集合,则该线程是不会被唤醒的
参考文献:《java并发编程之美》