Java多线程 之 wait、notifyAll(十二)

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方法的调用在有锁的上下文中)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值