wait/nofity
原理
WAITING状态的线程是获得了锁,但是又主动放弃了锁的。它会进入Monitor的WaitSet中排队等等。
而BLOCKED状态的是还没获得锁,在Monitor的EntryList中等待唤醒。
那WAITING怎么唤醒呢?只有在Owner调用notify
时,它们会去EntryList中排队,重新竞争锁。
API使用
obj.wait()
让进入 object 监视器的线程到 waitSet 等待obj.notify()
在 object 上正在 waitSet 等待的线程中挑一个唤醒obj.notifyAll()
让 object 上正在 waitSet 等待的线程全部唤醒
它们是线程间协作的方法,都是属性Object对象的方法,而且必须是获得对象锁才能调用 。
final static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
}).start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
}).start();
// 主线程两秒后执行
Thread.sleep(2);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
// obj.notify(); // 唤醒obj上一个线程
obj.notifyAll(); // 唤醒obj上所有等待线程
}
}
wait vs sleep
- sleep是Thread的方法,而wait是Object的方法
- sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用
- sleep在睡眠时,不释放锁,但wait在等待时会释放锁
- 它们的状态都是TIME_WAITING
注意虚假唤醒
错误唤醒等待的线程,就叫虚假唤醒。
因为nofity()是随机唤醒WaitSet中的一个线程,唤醒错了,这就不好处理了。
可以使用notifyAll()把waiting中的线程都唤醒了。
(但好像还是不能精准唤醒)
可以使用while包着wait,即使唤醒了,但是不符合条件,也会重新进入wait()
最佳实践
// 一个线程
synchronized(lock){
while(条件判断){
// ...
room.wait();
}
// 干活
}
// 另一个线程
synchronized(lock){
lock.notifyAll();
}
同步模式之保护性暂停
Guarded Suspension,用在一个线程等待另一个线程的执行结果。
要点
- 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject
- 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
- JDK 中,join 的实现、Future 的实现,采用的就是此模式
- 因为要等待另一方的结果,因此归类到同步模式
public class TestGuardObject {
public static void main(String[] args) {
GuardedObject guardedObject = new GuardedObject();
new Thread(()->{
System.out.println(guardedObject.get());
}, "t1").start();
new Thread(()->{
System.out.println("下载。。。");
guardedObject.complete(download());
}, "t2").start();
}
private static Object download() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new Dog();
}
}
class GuardedObject{
private Object response;
/**
* 获取结果
* @return
*/
public Object get(){
synchronized (this){
while (response == null){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
public void complete(Object response){
synchronized (this){
// 赋值
this.response = response;
this.notifyAll();
}
}
}
这样做还是有好处的,首先看t2,它放完值后可以做别的事,而别的线程不用等待。但是如果你用join
的话,只能等待它做完所有事情;而且,等待结果的变量不用设置成全局的,而是局部的。
超时版的get
与join
的源码一模一样。。。。
public Object get(long timeout){
synchronized (this){
long start = System.currentTimeMillis();
long passedTime = 0;
while (response == null){
long waitTime = timeout - passedTime;
// 经历的时间超过了最大等待时间
if(waitTime <= 0){
break;
}
try {
// 不能是timeout,小心虚假唤醒
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
passedTime = System.currentTimeMillis() - start;
}
return response;
}
}