等待通知机制
等待通知机制是一种并发编程模型。考虑这样一个问题:假设我们有一些生产者线程和消费者线程,生产者线程生产出来数据后,消费者线程如何得知有数据,可以消费了呢?最朴素的办法是使用轮询,但轮询难以保证及时性,而且还会消耗更多的处理器资源。等待通知机制就能代替轮询解决这个问题。
等待通知相关API
使用等待通知机制,我们需要一个对象作为锁,多个线程会持有同一个对象锁,等待函数和通知函数都实现在这个锁上。
wait():作用于锁对象,调用wait()后,当前线程进入等待状态(其实是进入一个等待队列)wait(long):作用于锁对象,给等待设置一个最长时间,如果这段时间过去后当前线程还是没有唤醒,就自动唤醒notify():作用于锁对象,对持有该锁的线程发出通知,解除等待状态,如果有多个线程处于等待状态,就随机通知一个notifyAll():作用于锁对象,对全部持有该锁的发出解除等待通知,谁先运行受优先级影响,但是最终还是要看JVM实现
等待通知代码例子
下面例子中我们实现了子线程等待通知机制。
MyThread.java
package com.gacfox.demo;
public class MyThread implements Runnable {
private final Object lock;
public MyThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
System.out.println("before");
try {
synchronized (lock) {
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after");
}
}
上面代码中定义了一个线程类,锁对象通过构造函数传入。这里要注意,wait()函数位于同步块内,也就是说等待操作必须同步执行,否则会抛出IllegalMonitorStateException的运行时异常。wait()执行完成后,锁会自动释放。
Main.java
package com.gacfox.demo;
public class Main {
public static void main(String[] args) throws InterruptedException {
final Object lock = new Object();
MyThread myThread = new MyThread(lock);
new Thread(myThread).start();
new Thread(myThread).start();
Thread.sleep(3000);
synchronized (lock) {
lock.notifyAll();
}
Thread.sleep(3000);
synchronized (lock) {
lock.notify();
}
}
}
主线程中,我们创建了两个同样的线程并同时启动,3秒后,使用notifyAll()通知所有线程解除等待状态,notify系列函数也必须位于同步块内。又过3秒后,虽然再次调用了notify(),不过由于已经没有线程等待了,所以没什么效果,这也说明多次调用notify()并不会引发异常。
这里要注意,执行notify()后,锁不会自动释放,必须执行完notify()同步块内所有代码后,才会释放。这是十分合理的,否则如果wait()不释放锁却让线程苦苦等待,锁就一直得不到释放。
此外,实际开发中要避免这样一种问题:notify()调用比wait()早,后调用的wait()可能永远也不会唤醒,这是程序BUG,应该仔细考虑,避免这种情况。
当wait遇到interrupt
前面我们介绍过,线程调用sleep()睡眠时被interrupt会抛出InterruptedException;wait()过程中也是一样的,该线程会立即抛出InterruptedException。