Java中的等待/通知机制是实现线程间通信的一种方式,是通过wait/notify/notifyAll等方法来完成的,这些方法又是什么含义呢?
下面罗列了一下:
方法名称 | 描述 |
notify() | 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到对象的锁 |
notifyAll() | 通知所有等待在该对象上的线程,但是这些线程还需要竞争去获取对象锁 |
wait() | 调用该方法的线程会进入WAITING状态,只有等待另外线程的通知或者被中断才能从该方法返回,需要格外注意,当调用wait()方法后,当前线程会释放对象的锁 |
wait(long) | 超时等待一段时间,这里的参数单位是毫秒,如果等待的时间超过了设定的超时时间还没有收到通知,则进行超时返回 |
wait(long,int) | 对于超时时间更细粒度的控制,可以达到纳秒 |
备注:在调用以上方法前,必须先获取对象锁,否则将会抛出IlligalMonitorStateException异常。
等待通知机制,是指一个线程A在调用wait()/wait(long)/wait(long,int)方法后进入释放持有的对象锁并进入等待状态,而另一个 线程B在获取到该对象锁后调用notify()/notifyAll()方法后释放对象锁通知线程A从wait()/wait(long)/wait(long,int)方法继续往下执行。线程A和线程B本身是独立的,但是借助于同一个对象锁及wait/notify机制来实现线程间通信和交互工作。
下面我们基于wait/notify机制来实现一个简单的示例。
package com.majing.java.concurrent;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class WaitNotifySample {
static Object lock = new Object();
static boolean flag = false;//是否具备执行某块逻辑的条件
public static void main(String[] args) {
Thread waitThread = new Thread(new Wait(),"WaitThread");
waitThread.start();
Thread notifyThread = new Thread(new Notify(),"NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable{
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread()+" hold the lock at time "+(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));
while(!flag){
System.out.println(Thread.currentThread()+" flag is false, wait at time "+(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//条件满足时,执行业务代码
System.out.println(Thread.currentThread()+" flag is true, running at time "+(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));
}
}
}
static class Notify implements Runnable{
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread()+" hold the lock at time "+(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));
System.out.println(Thread.currentThread()+" do some work.");
System.out.println(Thread.currentThread()+" notify at time "+(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));
lock.notifyAll();//不会释放lock锁
flag = true;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
System.out.println(Thread.currentThread()+" hold the lock again and do some other time at time "+(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));
}
}
}
}
执行效果如下:
Thread[WaitThread,5,main] hold the lock at time 2018-04-21 17:12:01
Thread[WaitThread,5,main] flag is false, wait at time 2018-04-21 17:12:01
Thread[NotifyThread,5,main] hold the lock at time 2018-04-21 17:12:01
Thread[NotifyThread,5,main] do some work.
Thread[NotifyThread,5,main] notify at time 2018-04-21 17:12:01
Thread[WaitThread,5,main] flag is true, running at time 2018-04-21 17:12:01
Thread[NotifyThread,5,main] hold the lock again and do some other time at time 2018-04-21 17:12:03
从上面可以看出,WaitThread线程先执行,但是因为条件未达到,所以在获取到对象锁之后又释放了相应的锁,这时候NotifyThread线程发现可以后去到对象锁,然后便do some work,并且变更了状态位flag,并且在执行完之后通过notifyAll给其他等待在该对象锁上的线程可以去竞争对象锁了,之后WaitThread获取到对象锁并且继续执行,在WaitThread线程执行完之后释放了对象锁,而NotifyThread线程重新获取对象锁然后做了其他的一些工作。
下面附上一张图,可以直观的看出wait/notify机制的原理(参考自:《Java并发编程的艺术》):
在每个对象的监视器锁上面都维护着两个队列,一个是同步队列,一个是等待队列。对于等待队列中的线程只有收到特定的通知信号才能迁移到同步队列。