wait/notify
wait/notify
等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
@Slf4j public class Test1_4_2 { public static void main(String[] args) throws InterruptedException { A1 a1 = new A1(); Thread t1 = new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.info("executing1"); a1.f1(); } }); Thread t11 = new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.info("executing11"); a1.f1(); } }); Thread t2 = new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.info("executing2"); a1.f2(); } }); t1.start(); t11.start(); Thread.sleep(50); t2.start(); } } @Slf4j class A1 { private Object obj1 = new Object(); public void f1() throws InterruptedException { // 1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁。 synchronized (obj1) { log.info("f1"); // 2)调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。 // 5)从wait()方法返回的前提是获得了调用对象的锁。 obj1.wait(); // obj1.wait(1000); log.info("f1-2"); } } public void f2() throws InterruptedException { // 1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁。 synchronized (obj1) { log.info("f2"); // 4)notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。 // obj1.notify(); obj1.notifyAll(); log.info("f2-2"); // 3)notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。 Thread.sleep(2000); log.info("f2-3"); } } }
上述例子主要说明了调用wait()、notify()以及notifyAll()时需要注意的细节,如下。
1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
2)调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
3)notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
4)notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。
5)从wait()方法返回的前提是获得了调用对象的锁。
从上述细节中可以看到,等待/通知机制依托于同步机制,其目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改。
为什么必须和synchronized一起使用
在 Java 里面,wait()和 notify()是 Object 的成员函数, 是基础中的基础。为什么 Java 要把wait()和 notify()放在如此 基础的类里面,而不是作为像 Thread 一类的成员函数,或者其他类 的成员函数呢?
在回答这个问题之前,先要回答为什么wait()和notify()必 须和synchronized一起使用?
上面代码的例子中 线程A调用f1(),线程B调用f2()。答案 已经很明显:两个线程之间要通信,对于同一个对象来说,一个线程 调用该对象的wait(),另一个线程调用该对象的notify(),该对象本身就需要同步!所以,在调用wait()、notify()之前,要先 通过synchronized关键字同步给对象,也就是给该对象加锁。
synchronized关键字可以加在任何对象的成员函 数上面,任何对象都可能成为锁。那么,wait()和notify()要同 样如此普及,也只能放在Object里面了。
为什么wait()的时候必须释放锁
当线程A进入synchronized(obj1)中之后,也就是对obj1上了 锁。此时,调用wait()进入阻塞状态,一直不能退出synchronized 代码块;那么,线程B永远无法进入synchronized(obj1)同步块里, 永远没有机会调用notify(),岂不是死锁了?
这就涉及一个关键的问题:在wait()的内部,会先释放锁obj1,然后进入阻塞状态,之后,它被另外一个线程用notify()唤醒, 去重新拿锁!其次,wait()调用完成后,执行后面的业务逻辑代 码,然后退出synchronized同步块,再次释放锁。
wait()内部的伪代码如下:
只有如此,才能避免上面所说的死锁问题。
参考: java并发编程的艺术 4.3.2 等待/通知机制
Java并发实现原理:JDK源码剖析 1.4 wait()与notify()