线程间等待与唤醒机制
由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知。但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。比如
球场上的每个运动员都是独立的“执行流” ,可以认为是一个 ”线程“。而完成一个具体的进攻得分动作,则需要多个运动员相互配合,按照一定的顺序执行一定的动作,线程1 先 “传球” ,线程2 才能 "扣篮“
完成上述所提的协调工作, 主要涉及到三个方法:
- wait() / wait(long timeout):让当前线程进入等待状态。
- notify() / notifyAll(): 唤醒在当前对象上等待的线程
注意: wait,notify,notifyAll 都是 Object 类的方法。
一、等待方法:wait()
wait 做的事情
- 使当前执行代码的线程进行等待. (把线程放到等待队列中)
- 释放当前的锁
- 满足一定条件时被唤醒, 重新尝试获取这个锁。
注意:wait 要搭配 synchronized 来使用,脱离 synchronized 使用 wait 会直接抛出异常。
wait结束等待的条件
- 其他线程调用该对象的 notify 方法;
- wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本,来指定等待时间)。
- 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出InterruptedException 异常。
我们使用下面的实例代码来演示wait()方法的使用
import javafx.beans.binding.ObjectExpression;
/**
* @author Zhang
* @date 2024/5/715:51
* @Description:
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object){
System.out.println("等待中");
object.wait();
System.out.println("等待结束");
}
}
}
------------------------------------------------------------
输出: 等待中
从上述代码可知,当代码执行到在执行到object.wait()之后就一直等待下去,那么程序肯定不能一直这么等待下去了。这个时候就需要使用到了另外一个方法唤醒的方法notify()。
二、唤醒方法:notify( )
notify 方法是唤醒等待的线程
.
- 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的。
- 其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
- 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁
我们使用下面的实例代码来演示notify()方法的使用:
- 创建 WaitTask 类, 对应一个线程, run 内部循环调用 wait;
- 创建 NotifyTask 类, 对应另一个线程, 在 run 内部调用一次 notify;
- 注意, WaitTask 和 NotifyTask 内部持有同一个 Object locker. WaitTask 和 NotifyTask 要想配合就需要搭配同一个 Object。
/**
* @author Zhang
* @date 2024/5/716:01
* @Description:
*/
public class Demo3 {
public static void main(String[] args) {
Object object = new Object();
Thread t1 = new Thread(()->{
synchronized(object){
System.out.println("wait 之前");
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait 之后");
}
});
Thread t2 = new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object){
System.out.println("进行通知");
object.notify();
}
});
t1.start();;
t2.start();
}
}
--------------------------------------------------------------
wait 之前
进行通知
wait 之后
三、唤醒方法:notifyAll( )
notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程
static class WaitTask implements Runnable {
private Object locker;
public WaitTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
while (true) {
try {
System.out.println("wait 开始");
locker.wait();
System.out.println("wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class NotifyTask implements Runnable {
private Object locker;
public NotifyTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
System.out.println("notify 开始");
locker.notifyAll();
System.out.println("notify 结束");
}
}
}
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(new WaitTask(locker));
Thread t2 = new Thread(new NotifyTask(locker));
t1.start();
Thread.sleep(1000);
t2.start();
}
---------------------------------
此时可以看到, 调用 notifyAll 能同时唤醒 3 个wait 中的线程
注意: 虽然是同时唤醒 3 个线程,但是这 3 个线程需要竞争锁。所以并不是同时执行,,而仍然是有先有后的执行。
三、经典面试题
nofify 和nofifyAll()的区别
- notify 只唤醒等待队列中的一个线程,其他线程还是乖乖等着。
- notifyAll 一下全都唤醒,需要这些线程重新竞争锁。
wait和sleep的区别
理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃执行一段时间。
具体区别:
- wait 需要搭配 synchronized 使用. sleep 不需要.
- wait 是 Object 的方法 sleep 是 Thread 的静态方法.
总结
以上就是今天要讲的内容,本文仅仅简单介绍了等待方法wait、唤醒方法notify、notifyAll。