等待通知机制

等待通知机制是一种并发编程模型。考虑这样一个问题:假设我们有一些生产者线程和消费者线程,生产者线程生产出来数据后,消费者线程如何得知有数据,可以消费了呢?最朴素的办法是使用轮询,但轮询难以保证及时性,而且还会消耗更多的处理器资源。等待通知机制就能代替轮询解决这个问题。

等待通知相关API

使用等待通知机制,我们需要一个对象作为锁,多个线程会持有同一个对象锁,等待函数和通知函数都实现在这个锁上。

  • wait():作用于锁对象,调用wait()后,当前线程进入等待状态(其实是进入一个等待队列)
  • wait(long):作用于锁对象,给等待设置一个最长时间,如果这段时间过去后当前线程还是没有唤醒,就自动唤醒
  • notify():作用于锁对象,对持有该锁的线程发出通知,解除等待状态,如果有多个线程处于等待状态,就随机通知一个
  • notifyAll():作用于锁对象,对全部持有该锁的发出解除等待通知,谁先运行受优先级影响,但是最终还是要看JVM实现

等待通知代码例子

下面例子中我们实现了子线程等待通知机制。

MyThread.java

package com.gacfox.demo;

public class MyThread implements Runnable {
    private final Object lock;

    public MyThread(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        System.out.println("before");
        try {
            synchronized (lock) {
                lock.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after");
    }
}

上面代码中定义了一个线程类,锁对象通过构造函数传入。这里要注意,wait()函数位于同步块内,也就是说等待操作必须同步执行,否则会抛出IllegalMonitorStateException的运行时异常。wait()执行完成后,锁会自动释放。

Main.java

package com.gacfox.demo;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        final Object lock = new Object();
        MyThread myThread = new MyThread(lock);

        new Thread(myThread).start();
        new Thread(myThread).start();

        Thread.sleep(3000);

        synchronized (lock) {
            lock.notifyAll();
        }
        Thread.sleep(3000);

        synchronized (lock) {
            lock.notify();
        }
    }
}

主线程中,我们创建了两个同样的线程并同时启动,3秒后,使用notifyAll()通知所有线程解除等待状态,notify系列函数也必须位于同步块内。又过3秒后,虽然再次调用了notify(),不过由于已经没有线程等待了,所以没什么效果,这也说明多次调用notify()并不会引发异常。

这里要注意,执行notify()后,锁不会自动释放,必须执行完notify()同步块内所有代码后,才会释放。这是十分合理的,否则如果wait()不释放锁却让线程苦苦等待,锁就一直得不到释放。

此外,实际开发中要避免这样一种问题:notify()调用比wait()早,后调用的wait()可能永远也不会唤醒,这是程序BUG,应该仔细考虑,避免这种情况。

当wait遇到interrupt

前面我们介绍过,线程调用sleep()睡眠时被interrupt会抛出InterruptedExceptionwait()过程中也是一样的,该线程会立即抛出InterruptedException

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。