Java并发编程——详解 wait() 、notify() 与 notifyAll()

问题引入

在线程运行时,可能会由于某些条件不满足,需要暂时让出锁资源,供其它线程使用,直到条件满足,再继续参与 CPU 调度,恢复运行


方法介绍

wait()

用于当前线程进入 WaitSet 等待,让出锁资源

可以传入参数,表示等待时间,如 wait(1000) 表示等待 1 秒后,若没有人唤醒它,则恢复运行

另外,wait() 相当于 wait(0)

notify()

随机唤醒当前锁对象上的 WaitSet 中的一个线程,进入 EntryList 参与 CPU 调度

notifyAll()

唤醒当前锁对象上的 WaitSet 中的所有线程,进入 EntryList 参与 CPU 调度

常见疑问

(1)为什么这三个方法都需要在获取对应锁对象的同步代码块中执行,否则会抛出 IllegalMonitorStateException

每一个锁对象,对应了一个 Monitor 监视器,结构如下

WaitSet 中保存了因 wait() 而被阻塞的线程,对应阻塞状态

EntryList 中保存了暂时没有分到时间片的线程,对应就绪状态

Owner 中保存了当前持有锁的线程

对于 wait() ,会让当前线程强制释放锁,并进入 WaitSet

对于 notify() 、notifyAll() ,需要唤醒 WaitSet 中的线程进入 EntryList

显然这些操作本质上都要对 Monitor 进行操作,故需要首先获取锁,找到对应的 Monitor 才能进行

(2)为什么这三个方法都定义在 Object 类而不是 Thread 类中

只有同一把锁上的调用 wait() 被阻塞的线程可以被 notify() 唤醒,对于不同锁的线程不起作用,而 Java 中任意对象都可以作为一把锁,所以这三个方法可以被任意对象调用,应定义在 Object 类中,线程通过锁对象找到对应的 Monitor

如果定义在 Thread 类中,线程在调用 wait() 和 notify() 的时候针对的是哪一把锁便无从判断,提高了管理上的难度

(3)wait() 与 sleep() 的区别

  • wait() 是 Object 类的方法,sleep() 是 Thread 类的方法
  • wait() 需要配合 synchronized 使用,sleep() 不需要
  • wait() 调用时,线程会释放锁资源,sleep() 不会释放锁资源

示例

public class WaitAndNotify {
	static final Object lock = new Object();
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread() {
			@Override
			public void run() {
				synchronized (lock) {
					System.out.println("t1 执行");
					try {
						lock.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("t1 继续执行");
				}
			}
		};

		Thread t2 = new Thread() {
			@Override
			public void run() {
				synchronized (lock) {
					System.out.println("t2 执行");
					try {
						lock.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("t2 继续执行");
				}
			}
		};

		t1.start();
		t2.start();

		Thread.sleep(1000);
		System.out.println("唤醒 lock 上的其他线程");
		synchronized (lock) {
			lock.notify();
			//lock.notifyAll();
		}
	}
}

t1 和 t2 线程调用 wait() 进入阻塞,主线程在 1 秒后唤醒 lock 上的其他线程


虚假唤醒(Spurious Wakeups)

问题描述

当一定条件满足时,通过 notifyAll() 唤醒所有线程,但其中可能只有一部分是真正需要被唤醒的线程,其余线程应该继续等待

解决方案

synchronized (lock) {
	while(条件不成立) {
		lock.wait();
	}
	//执行的内容
}

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

在需要进行条件判断的同步块中,使用 while 进行循环等待,如果条件不成立但被误唤醒,则继续等待,直到条件成立才执行

Guess you like

Origin blog.csdn.net/qq_25274377/article/details/120638200