wait()、notify()、notifyAll()都是Object的方法;
https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20%E5%B9%B6%E5%8F%91.md#wait-notify-notifyall
wait()
调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
-
在调用wait()之前,线程必须要获得该对象的对象级别锁,因此只能在同步方法或同步块中调用wait()方法,否则会在运行时抛出 IllegalMonitorStateException。
-
使用 wait()方法 挂起期间,当前线程会释放锁。
这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,就会造成死锁。
注意:
- 调用 wait()后,线程被挂起后,如果没有notify() 或者 notifyAll() 来唤醒挂起的线程,就会造成死锁。
- notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或 notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
wait()原理
monitor
monitor在JVM中是基于C++的实现的,ObjectMonitor中有几个关键属性:
_owner:指向持有ObjectMonitor对象的线程
_WaitSet:存放处于wait状态的线程队列
_EntryList:存放处于等待锁block状态的线程队列
_recursions:锁的重入次数
_count:用来记录该线程获取锁的次数
当多个线程同时访问一段同步代码时,首先会进入_EntryList队列中,当某个线程获取到对象的monitor后进入_Owner区域并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器_count加1。即获得锁。
若持有monitor的线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null,_count自减1,同时该线程进入_WaitSet集合中等待被唤醒。
若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)
notify 方法: 从等待队列中唤醒任意一个线程,使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知一个线程。
notifyAll 方法: 使所有正在等待队列中线程退出等待队列,进入就绪状态。
wait 方法: 阻塞
由此可知wait()原理:
The Owner中的线程,调用wait()后进入Wait Set,调用被notify()或者notifyAll()可以唤醒Wait Set中的一个或者所有线程。
调用wait()的前提是线程获取到了锁,并处于运行态(所以wait()方法必须用在同步方法或者同步代码块中),当调用wait()后,线程由运行态转变为阻塞态,需要被notify() 或者 notifyAll()唤醒
notify()
从等待队列中唤醒任意一个线程,使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知一个线程。
notifyAll()
使所有正在等待队列中线程退出等待队列,进入就绪状态。
sleep()与wait()区别
- sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用;
- sleep() 方法是线程类(Thread)的静态方法;wait()是Object对象的方法;
- sleep()会占着锁不放;wait()会释放锁;
使用案例
public class WaitNotifyExample {
public synchronized void before() {
System.out.println("before");
notifyAll();
}
public synchronized void after() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after");
}
}