文章目录
目标
本文最终目的是要熟练掌握 wait 和 notify 方法的各种使用场景, 最后再对 wait 和 sleep 进行比较区分.
为什么需要 wait 和 notify ?
在此之前, 我们控制线程之间的执行顺序的时候, 一是使用 sleep 让某一个线程进入休眠状态, 其他线程可以在此期间执行; 二是使用 synchronized 对线程中的某个部分加锁, 这时候其他线程再访问这个锁的时候就会发生阻塞等待.
但是使用这两种方法来对线程的执行顺序进行控制是比较麻烦的, 有时候也会出现不准确的情况. 对此, 我们有了一种更为合理的方法来让线程达到能够按照我们指定安排的顺序执行的效果. 那就是使用 wait 方法来让线程进行等待, 以及使用 notify 方法来对线程进行唤醒.
详解 wait 和 notify
wait (等待)
使用 wait 方法的前提是需要获取到锁(也就是说, wait 必然存在于 synchronized 中, 并且调用 wait 的对象和 synchronized 里使用的锁对象是一个对象的), 因为 wait 方法的功能是: 1. 释放原有的锁; 2. 等待通知(唤醒); 3. 当通知到达后, 尝试重新获取到锁(这就涉及到下面 notify 的东西了).
notify (唤醒)
使用 notify 方法则是起到了通知 / 唤醒的效果. 当然, 这里调用 notify 方法的对象需要和调用 wait 方法的对象是一样的(也就是和前面 synchronized 里使用的锁对象是一样的). 其次, notify 还有一个重要的特点: 就是在使用 notify 的时候, 不一定需要有 wait, 换句话说, 无论在什么时候使用 notify, 都是无副作用的.
一段代码了解 wait 和 notify 之间的运行过程
public class Main {
public static Object locker = new Object();
public static void main(String[] args) {
//创建一个用来等待的线程
Thread waitTask = new Thread(() -> {
synchronized (locker){
try {
System.out.println("wait之前");
locker.wait();
System.out.println("wait之后");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitTask.start();
//创建一个用来唤醒的线程
Thread notifyTask = new Thread(() -> {
//让用户控制, 输入内容之后再执行唤醒
Scanner scanner = new Scanner(System.in);
System.out.println("输入任意内容执行唤醒:");
scanner.next(); //此处next会发生阻塞,直到输入内容
synchronized (locker){
System.out.println("notify开始");
locker.notify();
System.out.println("notify结束");
}
});
notifyTask.start();
}
}
运行结果:
过程: 当 waitTask 线程执行到 wait 的时候, 会先释放 locker 锁, 然后进入阻塞等待, 当再执行接下来的 notifyTask 线程的时候, 执行到 notify 的时候, 就会唤醒之前的 wait, 将PCB由阻塞队列加入到就绪队列中来, 接着继续往下执行这个线程(这里面涉及到的锁对象都是 locker).
注意: wait 和 notify 机制还能非常有效地避免"线程饿死"的现象, 那么什么是"线程饿死"呢? "线程饿死"就是在某些情况下(具体就是操作系统对线程进行随机调度的原因), 有些线程反复占用CPU, 而有些线程则是始终都进不了CPU执行(正所谓旱的旱死, 涝的涝死), 简单来说就是调度器可能会出现线程分配不均匀的情况.
总结
wait 和 sleep 的区别(重要)
-
共同点: 它们都是使线程暂停一段时间的方法, 其中, 它们都可以进行死等, 也可以指定最长等待时间.
-
不同点:
- wait是Object类中的一个方法, sleep是Thread类中的一个方法
- wait必须在synchronized修饰的代码块或方法中使用, sleep方法可以在任何位置使用
- wait被调用后当前线程进入BLOCK状态并释放锁, 并可以通过notify和notifyAll方法进行唤醒; 而sleep被调用后当前线程进入TIMED_WAIT状态, 不涉及锁相关的操作