文章3:wait()、notify()和notifyAll()

77 阅读3分钟

wait()、notify() 和 notifyAll() 这三个方法都是属于 Object 类的方法,也就是说在 Java 中的任何对象都可以调用这三个方法。借由这三个方法的配合可以使得多线程可以更好的配合、协同工作。

wait()

wait() 方法的执行必须要在 synchronized 方法或者是代码块中才可以执行,由此可得:wait() 方法的执行前提是获取到 monitor 锁。执行 wait() 方法会使得当前线程释放掉 monitor 锁,强迫当前线程进入阻塞状态。

synchronized (object) {
    object.wait();
}

public synchronized void sync() throws InterruptedException {
    this.wait();
}

wait() 方法的重载:

  • wait(long timeout):在 timeout 毫秒内如果没有被 notify 唤醒则自行唤醒。
  • wait(long timeout, int nanos):在 1000000*timeout+nanos 纳米内如果没有被 notify 唤醒则自行唤醒。

notify()、notifyAll()

调用 wait() 方法当前线程会放弃 monitor 锁,相对应的调用 notify() 方法之后原来放弃 monitor 锁的线程就会重新去竞争 monitor 锁,竞争到锁的线程可以继续执行。

如果想要唤醒当前线程则必须是另外的一个线程去调用了 notiify() 并且轮到本线程被唤醒,或者是直接调用 notifyAll()。

notify() 和 notifyAll(): notify() 唤醒所有被 wait() 阻塞的线程之一。notifyAll() 唤醒全部被 wait() 阻塞的线程。

public class Demo implements Runnable {

    private final int num;
    public Demo(int num) { this.num = num; }

    private static final Object LOCK = new Object();
    private static final List<Integer> waitList = new ArrayList<>(50);
    private static final List<Integer> notifyList = new ArrayList<>(50);

    @Override
    public void run() {
        synchronized (LOCK) {
            try {
                waitList.add(num);
                LOCK.wait();
                notifyList.add(num);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 50; i++) {
            new Thread(new Demo(i)).start();
            Thread.sleep(10); // 一个个去睡觉
        }
        // notifyDemo();
        // notifyAllDemo();
        System.out.println(waitList);
        System.out.println(notifyList);
    }

    public static void notifyDemo() throws InterruptedException {
        for (int i = 0; i < 50; i++) {
            synchronized (LOCK) {
                LOCK.notify();
            }
            Thread.sleep(10); // 一个个来唤醒
        }
    }

    public static void notifyAllDemo() throws InterruptedException {
        synchronized (LOCK) {
            LOCK.notifyAll();
        }
        Thread.sleep(50); // 等待全部唤醒
    }
}

以上代码调用 notify() 方法的执行结果是:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]

调用 notifyAll() 方法的执行结果是:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
[49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

很明显,notify() 是顺序唤醒线程的,而 notifyAll() 是倒序唤醒线程的。如果想要深究这部分原因,进入到 Object 类中看到 wait()、notify()、notifyAll() 这三个方法都是被 native 修饰的方法。再深入就是 jvm 的 C++ 实现了,包括还得看 synchronized 的实现等等。这部分内容暂时道行还不够,就先不做介绍了,暂时就先记住这么个现象吧。

如果业务上需要对线程的唤醒顺序有要求的话,可以分用多个锁来指定唤醒对应线程。

public static void main(String[] args) {
    final Object LOCK = new Object();

    new Thread(() -> {
        synchronized (LOCK) {
            try {
                LOCK.wait();
                System.out.println("被唤醒。");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }).start();

    new Thread(() -> {
        synchronized (LOCK) {
            LOCK.notify();
            StringBuilder str = new StringBuilder();
            str.append("[");
            for (int i = 0; i < 100; i++) {
                str.append(i + 1);
                if (i != 99)
                    str.append(",");
            }
            str.append("]");
            System.out.println(str);
        }
    }).start();
}

再看上面这段代码,输出结果是:

[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99]
被唤醒。

显然对于 notify() 和 wait() 之间的执行顺序,调用 notify() 或者 notifyAll() 之后并不会马上释放本线程的 monitor 锁,而是等待被线程执行完毕之后再释放 monitor 锁给被 wait() 阻塞的线程去竞争。

总结

本文所讲述的三个方法都必须在 synchronized 代码块中执行,调用 wait() 方法必须会让当前线程主动挂起,调用 notify() 和 notifyAll() 会通知等待当前锁的线程重新抢锁,但要等当前线程执行完 synchronized 代码块后才释放掉当前锁给其他线程争抢,两者的区别是 notify() 一次唤醒一个线程,notifyAll() 一次唤醒全部线程。