文章目录
大家好~ , 这篇文章想给大家带来两个多线程中的关键字 : wait 和 notify , 他们俩的作用都是用来控制线程之间的执行顺序的 , 但是有什么区别 , 我们继续往下学习
推荐大家跳转到此页面进行查看~
上一篇文章的链接在这里~
七 . wait 和 notify
他们俩的作用是用来控制线程之间的执行顺序的
我们之前通过 join 实现了线程的结束顺序 , 但是远远不够
wait 和 notify 能够更好的控制线程之间的执行顺序
wait 叫做 等待 , 调用 wait 的线程就会进入阻塞等待的状态 (进入 WAITING 状态)
notify 叫做 唤醒 , 调用 notify , 就可以把对应的 wait 线程唤醒 , 让他从阻塞队列恢复到就绪状态
wait 和 notify 都是 Object 的成员方法 , 谁等待就由谁唤醒
o1.wait();
// 使用 o1.notify() 就可以唤醒 调用 o1.wait() 的线程
// 使用 o1.notify() 就不可以唤醒 调用 o1.wait() 的线程
对于 wait 来说 , 内部的执行过程 , 还有点复杂
- 释放锁
- 等待通知
- 当通知到达之后 , 就会被唤醒 , 尝试重新获取锁
对于 notify 来说 , 内部的执行过程非常简单 , 只有进行通知这一件事
我们先来看一眼 wait 是怎么回事
7.1 wait 方法
public class Demo17 {
public static void main(String[] args) throws InterruptedException {
// wait 方法是 Object 的成员方法,需要先创建 Object 实例
Object object = new Object();
System.out.println("wait 之前:");
object.wait();
System.out.println("wait 之后:");
}
}
我们需要在 synchronized
内部使用
public class Demo17 {
public static void main(String[] args) throws InterruptedException {
// wait 方法是 Object 的成员方法,需要先创建 Object 实例
Object object = new Object();
synchronized (object) {
System.out.println("wait 之前:");
object.wait();
System.out.println("wait 之后:");
}
}
}
我们也可以使用 jconsole 工具来看一下当前线程的状态
7.2 notify 方法
notify 方法也需要搭配 synchronized
使用
import java.util.Scanner;
// 创建两个线程,一个线程调用 wait,一个线程调用 notify
public class Demo18 {
// 这个对象用来做锁对象
public static Object Locker = new Object();
public static void main(String[] args) {
// t1 线程用来等待
Thread t1 = new Thread(() -> {
synchronized (Locker) {
System.out.println("wait 开始");
try {
Locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait 结束");
}
});
t1.start();
// t2 线程用来唤醒
Thread t2 = new Thread(() -> {
// 这里让用户输入内容之后,再执行通知
Scanner scanner = new Scanner(System.in);
System.out.println("输入任意内容,我们开启通知");
// 用户输入什么不重要
// next 会发生阻塞,直到用户真正输入内容以后
scanner.next();
synchronized (Locker) {
System.out.println("notify 开始");
Locker.notify();
System.out.println("notify 结束");
}
});
t2.start();
}
}
所以我们就可以控制线程的顺序了
线程 1 需要先计算出一个结果
线程 2 需要使用这个结果
我们就可以让线程 2 进行 wait , 线程 1 计算完结果之后 , notify 唤醒线程 2
本来线程1 线程2 是并发执行的 , 通过 wait notify 搭配使用 , 就可以实现正确的计算了
notify 这个东西 , 调用的时候 , 会尝试进行通知
如果当前对象没有在其他线程中 wait , 则不会有副作用
如果 wait 是一个对象 , notify 是另一个对象 , 则无法唤醒
import java.util.Scanner;
// 创建两个线程,一个线程调用 wait,一个线程调用 notify
public class Demo18 {
// 这个对象用来做锁对象
public static Object Locker1 = new Object();
public static Object Locker2 = new Object();
public static void main(String[] args) {
// t1 线程用来等待
Thread t1 = new Thread(() -> {
synchronized (Locker1) {
System.out.println("wait 开始");
try {
Locker1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait 结束");
}
});
t1.start();
// t2 线程用来唤醒
Thread t2 = new Thread(() -> {
// 这里让用户输入内容之后,再执行通知
Scanner scanner = new Scanner(System.in);
System.out.println("输入任意内容,我们开启通知");
// 用户输入什么不重要
// next 会发生阻塞,直到用户真正输入内容以后
scanner.next();
synchronized (Locker2) {
System.out.println("notify 开始");
Locker2.notify();
System.out.println("notify 结束");
}
});
t2.start();
}
}
7.3 wait 和 notify 还可以有效避免 “线程饿死”
线程饿死指的是 : 有些情况下 , 调度器可能分配任务不均匀 , 导致有些线程反复占用 CPU , 有些线程始终捞不着 CPU (涝的涝死 , 旱的旱死)
线程进入了 WAITING 状态 , 则务必需要其他的线程来主动唤醒 (notify)
线程进入了 BLOCKED 状态 , 则是其他线程把锁释放之后 , 操作系统负责唤醒
线程进入了 TIMED_WAITING 状态 , 操作系统会计时 , 时间到了之后来进行唤醒
这三个阻塞的状态都会导致 PCB 进入内核中对应的阻塞队列
7.4 notifyAll 方法
当有多个线程等待的时候, , notify 是一次唤醒一个 (从若干个里面随机挑一个)
notifyAll 则是唤醒所有 (再由这些线程去竞争锁)
notify 只唤醒等待队列中的一个线程 , 其他线程还是乖乖等着
notifyAll 一下全都唤醒 , 需要这些线程重新竞争锁
7.5 wait 和 sleep 的区别 (面试题)
他们俩都会让线程进入阻塞
但是阻塞的原因和目的不同 , 进入的状态也不同 , 被唤醒的条件也不同
进入的状态 : wait 对应的是 WAITING 状态 , sleep 对应的是 TIMED-WAITING 状态
被唤醒的条件 : wait 对应的是主动被唤醒 , sleep 对应的是时间到了就被自动唤醒
阻塞的原因和目的 : wait 是控制线程之间执行的先后顺序 , 而 sleep 实际开发很少用到
sleep 的目的是 “放权” , 暂时让出当前 CPU 的使用权
之所以 sleep 用的比较少 , 是因为等的时间太过于固定了 , 要是有突发情况想提前唤醒 , 并不是那么容易