等待/通知机制: 类似于生产者与消费者的关系。是指一个线程A调用了对象Object的wait()方法进入等待状态。另一个线程B调用notify()或notifyAll()方法,通知进入等待的线程。线程A收到通知后,从等待中返回,继续执行后面的操作。
一. 方法解释说明
1. wait() 方法
wait
public final void wait() throws InterruptedException
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的
行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify
方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有
权后才能继续执行。
对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
从JDK的官方文档中可以得出:
1. 当前线程必须拥有此对象监视器(监视器在下面解释);
2. 其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程才会醒来;
3. 该线程将等到重新获得对监视器的所有权后才能继续执行(重新获取锁);
4. 此方法应始终在循环中使用(因为需要等待被通知唤醒);
2. notify()方法
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
This method should only be called by a thread that is the owner of this object’s monitor. A thread becomes the owner of the object’s monitor in one of three ways:
此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:
1. By executing a synchronized instance method of that object.
2. By executing the body of a {@code synchronized} statement that synchronizes on the object.
3. For objects of type {@code Class,} by executing a synchronized static method of that class.
介绍完方法和Demo后在介绍监视器,晦涩难懂,在博客的尾部查看。
3. notifyAll()方法
唤醒在此对象监视器上等待的所有线程。
二. 三种方法的使用
此案例是一个快递业务,当送货的地点发生变化的时候,通知送货员
public class Express {
public static final String CITY = "Hongkong";
//地点
private String place = CITY;
/** 改变地址*/
public synchronized void changePlace(String place){
this.place = place;
notify();
}
/**地址改变做出响应*/
public synchronized void changePlaceResponse(){
while (this.place.equals(CITY)){
try {
wait();
System.out.println("thread :" + Thread.currentThread().getName() +", Change of place");
} catch (InterruptedException e) {
//Todo
}
}
System.out.println("thread :" + Thread.currentThread().getName() +", deal place: " + place +" call delivery");
}
static Express express = new Express();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new ThreadPlace().start();
}
Thread.sleep(1000);
express.changePlace("Beijing");
System.out.println("main thread over");
}
private static class ThreadPlace extends Thread{
@Override
public void run() {
express.changePlaceResponse();
}
}
}
此时只有一个线程被唤醒:
修改 notify();–> notifyAll(); 3个线程都被唤醒:
三. 监视器的理解
我们知道synchronized 用在方法上,与synchronized(this)等价,this可以省略。因此我们这样写并不会有什么问题。可是还是看不出监视器是什么,是怎么监视的。接下来我们修改等待和通知的方法进行测试。
public void changePlace(String place){
synchronized(this){
this.place = place;
notifyAll();
}
}
测试的结果是正常的。继续测试,修改changePlace中synchronized锁的对象:
似乎是这四个对象必须是同一个,才能起到监视的作用。是这样的吗?按照上面的推论,继续测试:
public void changePlace(String place){
synchronized(this.place){
this.place = place;
this.place.notifyAll();
}
}
/**地址改变做出响应*/
public void changePlaceResponse(){
synchronized(this.place){
while (this.place.equals(CITY)){
try {
this.place.wait();
System.out.println("thread :" + Thread.currentThread().getName() +", Change of place");
} catch (InterruptedException e) {
//Todo
}
}
System.out.println("thread :" + Thread.currentThread().getName() +", deal place: " + place +" call delivery");
}
}
发现什么问题了吗?感觉看起来这四个对象都是同一个,没有问题。运行一把:
结果仍然报了这个异常,难道刚刚的推论错了?!仔细分析发现this.place 的值已经被改变了,那吧18行与19行进行调换试下。果然有运行正常了。然后继续换其他的组合尝试,都没问题,屡试不爽。
总结: synchronized锁的对象与调用wait()等待和notify() /notifyAll() 通知的对象需保持一致!