线程开始运行,拥有自己的栈空间,就会按照既定的代码一步一步运行直到终止。如果每个运行中的线程仅仅是孤立地运行,那么没有一点价值,或者说价值很少,如果多个线程能够相互配合完成工作,这将会带来巨大的价值
--------《Java 并发编程的艺术》
———————————————————————————————————————————————————
本文主要介绍线程间通信的通知/等待机制:wait()、notify()/notifyAll()
一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行的又是另一个线程。应用到等待/通知机制,可以理解为:一个线程A调用的对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
划重点:wait()和notify/notifyAll()是对象的方法,是任意Java对象都具备的,也就是说这些方法都是定义在所有对象的超类java.lang.Object上。
我们先对这些方法做如下描述:
notify():通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获得了对象的锁;
notifyAll():通知所有等待在该对象上的线程;
wait():调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()法后,会释放对象的锁;
wait(long n):超时等待一段时间,这里的参数时间是毫秒,也就是说,等待n毫秒,如果没有通知就超时返回。
接下来,我借助一个例子来深入分析通知/等待机制,两个线程WaitThread和NotifyThread,前者检查flag是否为false,如果符合要求,进行后续操作,否则在lock上等待;后者在睡眠了一段时间后对lock进行通知。
public class WaitNotify{
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args){
Thread waitThread = new Thread(new wait(),"WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new notify(),"NotifyThread");
notifyThread.start();
}
static class wait implements Runnable{
public void run(){
synchronized(lock){
while(flag){
try{
System.out.println(Thread.currentThread() + "flag is true. wait @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
}catch (InterruptedException e){
}
}
System.out.println(Thread.currentThread() + "flag is false. running @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class notify implements Runnable{
public void run(){
synchronized(lock){
System.out.println(Thread.currentThread() + "hold lock. notify @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtils.second(5);
}
synchronized(lock){
System.out.println(Thread.currentThread() + "hold lock again. notify @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5);
}
}
}
}
输出如下:
其中SleepUtils是自己创建的线程休眠工具类。
上述输出第3行和第4行顺序可能会互换,通过这个例子主要是想说明调用wait()、notify()和notifyAll()时要注意的细节:
1、使用wait()、notify()和notifyAll()时需要先对调用对象加锁;
2、调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放入对象的等待队列中;
3、notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifyAll()的线程释放锁后,等待线程才有机会从wait()返回;
4、notify()方法将等待队列中的一个等待线程从等待队列移到同步队列中,而notifyAll()方法是把等待队列中的所有线程移动到同步队列中,被移动的线程状态由WAITING变为BLOCKED;
5、从wait()方法返回的前提是获得了调用对象的锁。
从以上细节可以看出,等待/通知机制依托于同步机制,其目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改。
可以总结出等待/通知的一般模式:
等待方遵循的原则:1)获取对象的锁;2)如果条件不满足,那么调用对象wait()方法,被通知后仍然要检查条件;3)条件满足则执行对应的逻辑。
通知方遵循的原则:1)获得对象的锁;2)改变条件;3)通知等待在对象上的线程;
以伪代码的形式给出:
等待方:
synchronized(对象){ while(条件不满足){ 对象.wait(); } 对应的逻辑处理 }
通知方:
synchronized(对象){ 改变条件; 对象.notify()/notifyAll(); }
小伙伴们有没有get到用boolean变量作为线程挂起与恢复的标识位的方法呢?
————————————————————————————————————————————————————
参考:方腾飞 《Java并发编程的艺术》