由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知。但是实际开发中有时候我们可以通过一些 api 让线程主动阻塞,从而控制多个线程之间的执行先后顺序.
完成这些操作就需要用到 wait,notify / notifyAll
注意: wait, notify, notifyAll 都是 Object 类的方法.
wait()方法
某个线程调用 wait 方法,就会进入阻塞(无论是通过哪个对象 wait 的),此时就处在 WAITING
wait 做的事情
1. 释放当前的锁
2. 进行阻塞等待
3. 收到通知后, 重新尝试获取这个锁.
因此 wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常(没锁怎么解锁)。
public class ThreadDemo16 {public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized (object) {System.out.println("wait 之前");object.wait();System.out.println("wait 之后");}}
}
这样在执行到object.wait()之后就一直等待下去,但程序肯定不能一直这么等待下去。这个时候就需要使用到了另外一个方法唤醒了,notify()。
notify()方法
notify 方法是唤醒等待的线程
public class ThreadDemo17 {public static void main(String[] args) throws InterruptedException {Object object = new Object();Thread t1 = new Thread(() -> {// 这个线程负责进行等待System.out.println("t1: wait 之前");try {synchronized (object) {object.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t1: wait 之后");});Thread t2 = new Thread(() -> {System.out.println("t2: notify 之前");synchronized (object) {// notify 务必要获取到锁, 才能进行通知try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}object.notify();}System.out.println("t2: notify 之后");});t1.start();// 此处写的 sleep 500 是大概率会让当前的 t1 先执行 wait 的.// 极端情况下 (电脑特别卡的时候), 可能线程的调度时间就超过了 500 ms// 还是可能 t2 先执行 notify.Thread.sleep(500);t2.start();}
}
如果 t2 不进行 notify ,此时 t1就会一直等。因此 wait 也提供了一个带参数的版本,可以指定最大等待时间。
这个带有等待时间的版本和 sleep 有点像。他们都能指定等待时间,也都能被提前唤醒 wait 是使用 notify 唤醒,sleep 是使用 interrupt 唤醒,但这里的含义却截然不同。notify 唤醒 wait 不会有任何异常;interrupt 唤醒 sleep 则是出异常了。
如果有多个线程等待 object 对象,此时只有一个线程 object.notify(),会随机唤醒一个等待的线程(不知道具体哪个)。但是可以用多组不同的对象来唤醒指定的线程。
在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
public class ThreadDemo18 {// 有三个线程, 分别只能打印 A, B, C. 控制三个线程固定按照 ABC 的顺序来打印.public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {System.out.println("A");synchronized (locker1) {locker1.notify();}});Thread t2 = new Thread(() -> {synchronized (locker1) {try {locker1.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("B");synchronized (locker2) {locker2.notify();}});Thread t3 = new Thread(() -> {synchronized (locker2) {try {locker2.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("C");});t2.start();t3.start();//这样子是为了防止 t1 先通知了,t2 和 t3 就会死等Thread.sleep(100);t1.start();}
}
wait 结束等待的条件
其他线程调用该对象的 notify 方法.
wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.
P 打算去 ATM 上取点钱,但是这时候显示没钱了,这时候 P 就要出去等在外面排队的人进去查余额、存款、转账......结果在排队的人都是取钱的,这时候运钞车就来了,工作人员把钱放进去之后说:可以了。这时候就通知了外面的人可以取钱了,先进去取的人就结束等待,没进去的人只能在外面阻塞。
notifyAll()方法
和 notify 非常相似。多个线程 wait 的时候,notify 是随机唤醒一个,notifyAll 是所有的线程都唤醒,然后这些线程再一起竞争锁。
notify 只唤醒等待队列中的一个线程. 其他线程还是乖乖等着
notifyAll 一下全都唤醒, 需要这些线程重新竞争锁
wait 和 sleep 的对比(面试题)
其实理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃执行一段时间.
当然为了面试的目的,我们还是总结下:
1. wait 需要搭配 synchronized 使用. sleep 不需要.
2. wait 是 Object 的方法 sleep 是 Thread 的静态方法.