引入 wait 和 notify 是为了能够从应用层面,干预多个不同线程的执行顺序。这里说的干预,不是影响系统的线程调度策略(仍是随机调度),而是相当于在应用程序代码中,让后执行的线程,主动放弃被调度的机会,就可以让先执行的线程先执行完对应代码了。

1. wait() 方法

wait 内部做的三件事

  1. 释放锁
  2. 进入阻塞等待
  3. 当其他线程调用 notify 的时候,wait 解除阻塞,并重新尝试获取这个锁

【注意】wait 方法必须在 synchronized 里面使用

wait 结束等待的条件:

  1. 其他线程调用该对象的 notify 方法
  2. wait 等待时间超时(wait 方法提供一个带有 timeout 参数的版本,用来指定等待时间)
  3. 其他线程调用该等待线程的 interrupt 方法,导致 wait 抛出 InterruptedExpection 异常

2. notify() 方法

notify 方法用来唤醒等待中的线程,例如:

public static void main(String[] args) {
	Object locker = new Object(); //定义锁对象

	Thread t1 = new Thread(() -> {
		synchronized(locker) {
			System.out.println("t1 wait 之前");
			//抛出异常
			try {
				locker.wait(); //用锁对象调用 wait
			}catch {
				throw new RunrimeExprction(e);
			}
			System.out.println("t1 wait 之后");
		}
	});

	Thread t2 = new Object(() -> {
		try {
			//sleep 必须写在 synchronized 外面,保证 t1 先拿到锁
			Thread.sleep(5000);
			synchronized(locker) {
				System.out.println("t2 notify 之前");
				locker.notify(); //用同一个锁对象调用 notify
				System.out.println("t2 notify 之后");
			}
		}catch {
				throw new RunrimeExprction(e);
		}	
	});
	t1.start();
	t2.start();
}

【注意】notify 方法也要在 synchronized 里面使用
wait 放到 synchronized 里面是因为要释放锁的前提是有锁;notify 其实可以不放到 synchronized 里,但是 Java 中特别约定要把 notify 放到 synchronized 中。

【注意】要确保代码先执行 wait 后执行 notify,否则 wait 无法被唤醒,代码逻辑上是有问题的

代码运行结果:
在这里插入图片描述

运行结果分析:

  1. t2 休眠 5s,保证了 t1 先拿到锁,打印 “t1 wait 之前”
  2. t1 执行到 wait,使 t1 释放锁,t2 结束休眠后拿到锁,打印 “t2 notify 之前”
  3. t2 执行到 notify,唤醒了 t1,但此时 t2 还没有释放锁,t1 处于阻塞状态,打印 “t2 notify 之后”
  4. t2 执行结束,释放锁,t1 打印 “t1 wait 之后”

wait 和 notify 之间是通过 Object 对象之间联系起来的,调用两个方法的对象必须一致才能实现唤醒

多个 wait :

  1. 如果是不同对象调用的, 使用对应的对象调用 notify 实现唤醒
  2. 如果是同一对象调用的,使用 notify 会随机唤醒其中一个

3. notifyAll() 方法

notifyAll 方法用来唤醒这个对象上所有等待的线程(不常用)
【注意】这些线程 wait 返回的时候,要重新获取锁,就会产生锁竞争,导致这些线程实际上是一个一个串行执行的

4. wait 和 sleep 比较

4.1 相似点

  1. wait 提供了一个带有超时时间的版本,sleep 也是指定时间,都可以让线程阻塞一定时间,时间到了,继续执行
  2. wait 和 sleep 都能被提前唤醒,wait 通过 notify 或 interrupt 唤醒,sleep 通过 interrupt 唤醒

4.2 不同点

  1. wait 需要搭配 synchronized 使用,sleep 不需要
  2. wait 是 Object 类提供的方法,sleep 是 Thread 类提供的静态方法
  3. 使用 sleep 一般是在直到要阻塞多长时间的情况下,达到时间停止休眠;使用 wait 是在不知道要阻塞多久的情况下,wait 指定的超时时间是用来“兜底”的,不期望达到这个时间,一般在达到这个超时时间之前就被唤醒了
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐