一、初识 wait、notify、notifyAll
我们知道由于线程的抢占式执行导致线程之间的调度是随机的,无序的。但是在一些场景下我们有需要合理的协调多个线程的执行顺序。我们知道使用 join 可以控制线程执行顺序,但是 join
只能让一个线程执行完在执行另外一个线程,功能有限。因此我们引入了 wait
和 notify/notifyAll
这样一组API用来更灵活地控制线程执行的顺序。
举个例子:
![file](https://pottercoding.cn/wp-content/uploads/2024/04/image-1712324623190.png)
针对上述情况,我们可以使用 wait/notify
有效解决:
假如张三发现ATM没钱,就(wait)释放锁走出ATM机,并进行阻塞等待(暂时不参与CPU调度,不参与锁竞争),直到有人向ATM机存钱后,此时时机成熟,就可以唤醒(notify)张三,继续参与ATM机锁竞争。
二、wait、notify、notifyAll 功能介绍
1、wait()
wait()
:发现条件不满足/时机不成熟,就调用 wait() 阻塞等待,同时释放当前锁,当满足一定条件时可以被唤醒,重新尝试获取这个锁。
注:使用 wait() 阻塞等待会让线程进入WAITING状态
wait等待结束条件:
- 其他线程调用该对象的 notify 方法.
- wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
- 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.
2、notify()
notify()
:其他线程构造了一个成熟的条件,就可以调用 notify() 唤醒它。
notify注意事项:
- 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
- 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
- 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行 完,也就是退出同步代码块之后才会释放对象锁。
3、notifyAll()
notifyAll()
:notify() 只是唤醒某一个等待线程。使用 notifyAll() 可以一次唤醒所有的等待同一对象的线程。
notifyAll注意事项:
虽然是同时唤醒 多个线程, 但是这 多个线程需要竞争锁,所以并不是同时执行,而仍然是先获取锁的先执行。
4、wait、notify、notifyAll 要点总结
- wait/notify/notifyAll 都是Object 的方法,因此只要是个类对象都可以使用 wait、notify、notifyAll。
- wait/notify/notifyAll 要搭配 synchronized 来使用. 脱离 synchronized 使用会直接抛出异常:
IllegalMonitorStateException
- synchronized 的锁对象必须和调用 wait/notify/notifyAll 方法的对象是同一个。
- 针对某一加锁对象,先执行wait 然后 notify/notifyAll,此时才有效果,如果没有 wait 就 notify 相当于“一炮打空”,此时 wait 无法唤醒,不过代码不会出现其他异常。
5、wait/notify 使用示例
public class ThreadExample_wait_notify {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t1 = new Thread(()->{
synchronized (object) {
try {
System.out.println("wait开始");
object.wait();
System.out.println("wait结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(()->{
synchronized (object) {
System.out.println("notify开始");
object.notify();
System.out.println("notify结束");
}
});
// 等待t1中wait解锁阻塞,否则t2中的notify没有实质效果
t1.start();
Thread.sleep(1000);
t2.start();
}
}
![file](https://pottercoding.cn/wp-content/uploads/2024/04/image-1712324642838.png)
结果说明:
上述代码 t1 先获取到锁,执行到 wait 时 t1阻塞解锁,1秒后,t2获得锁,执行到 notify 时 t1 线程就会被唤醒,但是notify后不会立即释放锁,当 t2 代码块中的逻辑执行完后才释放锁,t1 线程才能获取锁继续向下执行。
三、wait、join、sleep 归纳
(1)wait
wait()
方法是 Object类 提供的实例方法,可以使线程进入等待状态,直到其他线程调用了该对象的notify()或notifyAll()方法。通常被用于线程间通信,如生产者-消费者模式中(后续介绍),消费者需要等待生产者通知有新数据可取。当线程调用 wait() 方法时,它会释放占据的锁,并且线程的状态为WAITING,直到notify()或notifyAll()方法被调用。(2)join
join()
方法是 Thread类 提供的方法静态方法,用于等待被调用线程执行完毕。在主线程中调用了子线程的join() 方法后,主线程会进入 WAITING 状态,直到子线程执行完毕才会继续执行。可以用来保证多个线程按照指定顺序执行。(3)sleep
sleep()
方法也是 Thread类 提供的实例方法,它可以使当前线程暂停执行一段时间。当线程调用 sleep() 方法时,它不会释放锁,线程的状态为 TIMED_WAITING 。通常被用于控制程序执行的速度或时间,或常常在循环内部以等待某些条件的发生。
总的来说,wait() 方法是用于线程间的通信,join() 方法是用于等待其他线程执行完毕,sleep()方法是用于暂停当前线程的执行。在使用上, wait 需要搭配 synchronized 使用,sleep 和 join 则不需要。总之本质上没有可比性,唯一的相同点就是都可以让线程放弃执行一段时间。它们的使用 场景 不同,需要根据实际需求选择合适的方法。