在实际的开发中我们有时候希望合理的协调多个线程之间的执行先后顺序,这个时候就要涉及到3个方法了:
- wait() / wait( long timeout) :让当前线程进入等待状态
- notify() / notifyAll():唤醒在当前对象上等待的线程
- 注意:上述三个方法都是 Object 类的方法
1. 注意事项
先看一段代码
public class Demo24 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
System.out.println("wait 之前");
object.wait();
System.out.println("wait 之后");
}
}
通过结果可以看出!此时抛出了一个异常,这是因为这把锁都没获取到,你就尝试解锁,所以wait()必须是写在 synchronized 代码块里面! notify()也是必须写在 synchronized 代码块里面!
wait 主要做3件事:
1.解锁
2.阻塞等待
3.当收到通知的时候,就唤醒,同时尝试重新获取锁~
public class Demo24 {
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
try {
System.out.println("wait 开始");
synchronized (locker) {
locker.wait();
}
System.out.println("wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
Thread.sleep(1000);
Thread t2 = new Thread(() -> {
try {
synchronized (locker) {
System.out.println("notify 开始");
locker.notify();
System.out.println("notify 结束");
}
} catch (Exception e) {
e.printStackTrace();
}
});
t2.start();
}
}
上述代码的执行流程是,当 t1 先执行,执行到 wait 就阻塞了,然后1s之后,t2 开始执行,执行到了notify 就会通知 t1 线程唤醒!但是此时 notify 是在 synchronized 内部,就需要 t2 释放锁后,t1 才能继续往下走。
在上述代码中,虽然 t1 先执行的,但是我们可以通过 wait notify 控制 让 t2 先执行一些逻辑,在 t2 执行完之后,notify 唤醒 t1,t1 再继续往下执行。
- wait 也提供了一个带参数的版本,参数指定的是最大等待时间,如果超过这个时间,就会自动唤醒
- 使用 wait 阻塞等待会让线程进入 WAITING 状态。
- 唤醒操作,还有个 notifyAll,如果多个线程都调用了 object.wait ,此时 main 方法中调用 object.notify 会随机唤醒上述的一个线程(另外两个仍然是 WAITING 状态);如果是 调用 object.notifyAll,此时会唤醒全部线程,此时多个线程之间就会重新竞争锁!
2. sleep 和 wait 区别 (经典面试题)
首先最大的区别是设计的初心不同~
- wait 解决的是线程之间的顺序控制
- sleep 单纯是让当前线程休眠一会
使用上也有区别
- wait 要搭配 synchronized 使用
- sleep 不需要
所属的类不同
- wait 是 Object 所属方法
- sleep 是 Thread 类的静态方法