线程间通信之等待唤醒机制
在命令式编程中,线程之间的通信机制有2种:共享内存和消息传递。
在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。在消息传递的并发模型里,线程之间内有公共状态,线程之间必须通过发送消息来进行显示通信。
等待唤醒机制
等待唤醒机制,是指一个线程A调用了对象Object的wait()
方法进入等待状态,而另一个线程B调用了对象Object的notify()
或者notifyAll()
方法,线程A收到了通知后,从对象Object的wait()
方法返回,进而执行后续操作。
上述两个线程通过Object对象来完成交互,而对象上的wait()
和notify()/notifyAll()
的关系就如同开关信号一样,用来完成等待方和通知方的交互工作。
等待/通知的相关方法是任意Java类对象都具备的,Object类都有实现
等待/通知的相关方法
notify()
: 通知一个对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获得了对象的锁。notifyAll()
:通知所有等待在该对象上的线程wait()
:调用该方法的线程进入等待状态,只有等待另外的线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象锁。wait(long mills)
:超时等待一段时间(单位毫秒),如果没有就超时返回。wait(long mills , int n)
:对超时时间更细粒度的控制,可以达到纳秒
调用wait()
和notify()/notifyAll()
需要注意的细节:
使用
wait()
和notify()/notifyAll()
时需要先对调用对象加锁。调用
wait()
方法后,线程状态由运行状态(Running)变为等待状态(Waiting),并将当前线程放置到对象的等待队列。notify()/notifyAll()
方法调用后,等待线程依旧不会从wait()返回,需要调用notify()/notifyAll()
的线程释放锁之后,等待线程才有机会从wait()返回。notify()
方法将等待队列中的一个等待线程移动到同步队列中,而notifyAll()
方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程有等待状态(Waiting)变为阻塞状态(Blocked)。从
wait()
方法返回的前提是获得了调用对象的锁。
从上述细节中可以看到,等待唤醒机制依托于同步机制(同步对象锁),其目的就是确保wait()
方法返回时能够感应到通知线程对变量做出修改。
在Thread的API与wait()
有一个很像的方法sleep()
方法,他们2者的区别如下:
sleep()在休眠的过程中是一直持有锁的,而wait()是执行之后释放锁的。
等待/通知机制示例:
public class Test {
private final static Object lock = new Object();
private static boolean flag = true;
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
threadA.setName("threadA");
ThreadB threadB = new ThreadB();
threadB.setName("threadB");
threadA.start();
threadB.start();
}
public static class ThreadA extends Thread {
@Override
public void run() {
synchronized (lock) {
while (flag) {
System.out.println(getName() + ":开始执行");
try {
System.out.println(getName() + ":进入等待状态");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName() + ":执行结束");
}
}
}
public static class ThreadB extends Thread {
@Override
public void run() {
synchronized (lock) {
System.out.println(getName() + ":开始执行");
lock.notify();
System.out.println(getName() + ":通知释放锁");
flag = false;
System.out.println(getName()+":执行结束");
}
}
}
}
打印结果:
threadA:开始执行
threadA:进入等待状态
threadB:开始执行
threadB:通知释放锁
threadB:执行结束
threadA:执行结束
在这个示例中,ThreadA和ThreadB的状态描述:
当ThreadA线程获取了lock对象锁,然后调用lock.wait()
方法。从而放弃了lock锁,ThraedA线程进入等待队列中(等待池),ThreadA进入等待状态。由于ThreadA释放了锁,此时ThreadB获得了lock锁,并调用notify()
方法,ThreadA从等待队列(等待池)进入同步队列(锁池),此时ThreadA由等待状态(Waiting)变为阻塞状态(Blocked)。ThreadB释放了lock锁后,ThreadA再次获取了lock锁,wait()
方法返回继续执行。