1.wait简答介绍
前面的介绍都是关于线程同步的,也就是说,多个线程不能同时访问共享资源,但是后面的几篇博文将介绍线程之间的协作。
注意:sleep、yield调用并没有释放锁。
在多线程协作中,最经典的问题就是生产者-消费者问题了。只有生产者生产出了产品消费者才能去消费。因此,在生产者生产出产品之前消费者需要等待阻塞,当生产者生产者生产出产品之后可以通知消费者去消费。
wait方法会等待外部世界发生变化的时候将任务挂起,并且只有在notify或notifyAll发生时,即发生了某些感兴趣的事物,这个任务才会被唤醒并去检查所产生的变化。
注意:wait方法会释放锁。因此其他线程可以获取锁去执行。
wait方法有两个版本:
(1)接受毫秒数作为参数,表示在此期间等待。
在wait期间对象锁是释放的;可以接收到notify、notifyAll消息或者时间到期,从wait中恢复执行。
(2)不接受任何参数
wait会无限等待下去,直到接收到notify、notifyAll消息。
wait、notify、notifyAll是基类Object的一部分。因为这些方法操作的锁是所有对象的一部分。由于要操作锁,所以,wait、notify、notifyAll这三个方法只能在同步方法或者同步块内才能被调用。sleep方法不需要操作锁,因此可以在任何地方调用。但是在非同步代码中调用wait、notify、notifyAll不会有编译错误,但是运行时会有IllegalMonitorStateException异常,“当前线程不是拥有者”,表示,调用wait、notify、notifyAll的任务在调用这些方法之前必须“获取”对象的锁。
如果要向对象x发送notifyAll,必须在能够取得x锁的同步控制块中这么做:
synchronized(x) {
x.notifyAll();
}
必须用一个检查感兴趣的条件的while循环包围wait。
而且在使用wait、notify、notifyAll进行线程间协作时,要注意代码的书写,不当的代码有可能会错失信号,从而导致死锁。
2.wait造成的死锁
考虑下面的情况:
//Task1
synchronized(sharedMonitor) {
<setup condition for Task2>
sharedMonitor.notify();
}
//Task2
while(someCondition) {
//Point 1
synchronized(sharedMonitor) {
sharedMonitor.wait();
}
}
假设,Task2 while判断条件成立,运行到Point 1时切换到Task1,Task1修改了条件,并且执行了notify,这时切换到Task2执行,这时Task2会直接进入wait,错过了Task1的notify。这样会导致死锁。因为,Task1此时肯定也在等待Task2执行之后改变条件。这个的理解可以参考《Thinking in java》第四版中文版P704-705的关于给车打蜡、抛光的例子,下面已经将该例子实现。
修正上面的问题:
//Task2
synchronized(sharedMonitor) {
//Point 1
while(someCondition) {
sharedMonitor.wait();
}
}
这时,如果Task2先执行,在wait处阻塞等待,这时Task1执行唤醒Task2;如果Task1先执行,修改了条件,当Task2执行时,while判定条件不成立,wait不会执行。都不会造成死锁。
3.wait示例
package org.fan.learn.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Created by fan on 2016/7/4.
*/
class Car {
private boolean waxOn = false;
//同步方法
//打蜡
synchronized void waxing() {
waxOn = true;
notifyAll();
}
//同步方法
//抛光
synchronized void buffing() {
waxOn = false;
notifyAll();
}
//同步方法
//等待打蜡完成
synchronized void waitForWaxing() throws InterruptedException {
while (!waxOn) {
wait();
}
}
//同步方法
//等待抛光完成
synchronized void waitForBuffing() throws InterruptedException {
while (waxOn) {
wait();
}
}
}
//打蜡任务
//一次打蜡完成,需要等待抛光完成之后才能继续打蜡
class WaxingTask implements Runnable {
private Car car;
public WaxingTask(Car car) {
this.car = car;
}
public void run() {
try {
while (!Thread.interrupted()) {
System.out.println("waxing");
TimeUnit.SECONDS.sleep(2);
car.waxing();
car.waitForBuffing();
}
} catch (InterruptedException e) {
//打印异常堆栈
e.printStackTrace();
}
}
}
//抛光任务
//在抛光之前必须要先打蜡,需要等待打蜡完成才能抛光
class BuffingTask implements Runnable {
private Car car;
public BuffingTask(Car car) {
this.car = car;
}
public void run() {
try {
while (!Thread.interrupted()) {
car.waitForWaxing();
System.out.println("Buffing");
TimeUnit.SECONDS.sleep(2);
car.buffing();
}
} catch (InterruptedException e) {
//打印异常堆栈
e.printStackTrace();
}
}
}
public class WaxingBuffing {
public static void main(String[] args) throws InterruptedException {
Car car = new Car();
ExecutorService exe = Executors.newCachedThreadPool();
exe.execute(new WaxingTask(car));
exe.execute(new BuffingTask(car));
TimeUnit.SECONDS.sleep(5);
//向所有任务发送interrupt()信号
exe.shutdownNow();
System.out.println("main exit");
}
}
某次代码执行的输出如下:
waxing
Buffing
waxing
java.lang.InterruptedException
main exit
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at org.fan.learn.thread.Car.waitForWaxing(WaxingBuffing.java:23)
at org.fan.learn.thread.BuffingTask.run(WaxingBuffing.java:62)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at org.fan.learn.thread.WaxingTask.run(WaxingBuffing.java:42)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
从上面的输出可以看出,BuffingTask是在wait过程中被中断的,WaxingTask是在sleep过程中被中断的。
从而也验证了,wait和sleep是可以被中断的。
注意:
(1)wait()的使用方法。
(2)wait,notifyAll在Synchronized方法中。
notifyAll并不会唤醒所有等待的任务,而是仅仅唤醒等待这个锁的任务(notifyAll方法的调用在有锁的上下文中)。