转载

Why wait/notify must be called in a synchronized block

在 Java 并发编程中,调用 Object wait/notify 方法的代码段必须要被包含在 synchronized 块中,接着即是耳熟能详的:调用 wait 方法时,先释放锁,然后线程进入阻塞状态,直至被 notify,然后重新尝试获得锁。看似一气呵成的一顿猛如虎的操作,其个中缘由到底是什么?有些我们看起来理所当然的东西,难道真的是理所当然的吗?本篇文章即谈谈,为什么调用 wait/notify 方法的代码段必须要被包含在 synchronized 块中。

先从 官方文档 入手,官方文档展示了一段代码告诉读者们 wait 方法应如何使用。如果当前线程(即调用 obj.wait 方法的线程)不是 monitor 的 owner,抛出 IllegalMonitorStateException。synchronized 关键字在经过编译后,会在同步代码块的前后生成 monitorenter 和 monitorexit 这两个字节码指令,它们都需要一个 reference 类型的参数指明要锁定和解锁的对象,这也是「monitor」的由来。

synchronized (obj) {
    while (<condition does not hold>)
        obj.wait(timeout);
    ... // Perform action appropriate to condition
}

假设要利用 wait/notify 方法实现 BlockingQueue 数据结构,很快的写下了如下代码。

public class BlockingQueue {

    private Queue<String> buffer = new LinkedList<String>();

    public void put(String data) {
        buffer.add(data);
        notify();
    }

    public String get() throws InterruptedException {
        while (buffer.isEmpty()) {
            wait();
        }
        return buffer.remove();
    }
}

注意!BlockingQueue 在调用 wait/notify 方法时并没有使用 synchronized 关键字,思考会发生什么问题,考虑以下场景。

  • consumer 线程调用 get 方法,反复测试 buffer.isEmpty( )。
  • 在 consumer 线程调用 wait 方法之前,producer 线程调用了 put 方法将 data 添加至 buffer,并调动了 notify 方法。
  • 此时 consumer 调用了 wait 方法,但是很不幸 consumer 错失了来自 producer 的 notify。
  • 更不幸的,不再有 producer 往 buffer 添加新的 data,consumer 会永远阻塞于 wait 方法,虽然实际上此时 buffer 中存在一个 data。

产生上述场景所描述问题的关键原因在于 put & notify 和 get & wait 不被原子性(atomic)的调用。很自然的 synchronized 可以发挥保证原子性的作用。但是为了保证原子性,synchronized 完全可以使用任意一个 Object,为什么必须和 wait/notify 方法一致的 Object?其实很简单,结合以下代码:如果使用 BlockingQueue.class 作为锁,当 wait 方法释放 this 锁并进入阻塞状态后,get 方法依旧拥有着 BlockingQueue.class 锁,producer 永远会被阻塞于调用 put 方法,形成死锁。

public class BlockingQueue {

    private Queue<String> buffer = new LinkedList<String>();

    public void put(String data) {
        synchronized (BlockingQueue.class) {
            buffer.add(data);
            notify();
        }
    }

    public String get() throws InterruptedException {
        synchronized (BlockingQueue.class) {
            while (buffer.isEmpty()) {
                wait();
            }
            return buffer.remove();
        }
    }
}

EOF.

原文  http://tinylcy.me/2018/why-wait-and-notify-must-be-called-in-a-synchronized-block/
正文到此结束
Loading...