故事引入
wait / notify 原理
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
WaitSet 里的线程是获取过锁 又放弃了,EntryList 中的是还没有获得锁 - BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间
- BLOCKED 线程会在 Owner 线程释放锁时被唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但并不意味着会立即获得锁,仍然会进入 EntryList 重新竞争
wait / notify API
- obj.wiat() 让进入 obj 监视器的线程到 WaitSet 等待
- obj.notify() 选中 obj 监视器的 WaitSet 中的某个线程被唤醒
- obj.notifyAll() obj 监视器的 WaitSet 中的全部线程都被唤醒
他们都是线程之间进行协作的手段,都属于 obj 对象的方法。必须获得此对象的锁,才能调用这两个方法。(线程只有变成 obj 对象的监视器的 Owner 才能调用)
/**
* 1、wait / notify / notifyAll
* 2、带一个参数的 wait :等待设定时间后,如果没有被其他线程唤醒,就不等了,继续向下执行;如果还没到等待时间,另一个线程来唤醒,就被唤醒,继续向下执行;
* 3、带两个参数的 wait:wait(毫秒,纳秒),不会精确到纳秒,多一毫秒;
*/
public class BiasedDemo5 {
final static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (lock){
System.out.println("线程 t1 开始执行");
try {
lock.wait();
// lock.wait(1000); // 即使没有其他线程唤醒,1s 后 继续执行后续代码
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 t1 完成其他业务");
}
},"t1").start();
new Thread(()->{
synchronized (lock){
System.out.println("线程 t2 开始执行");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 t2 完成其他业务");
}
},"t2").start();
Thread.sleep(2000);
synchronized (lock){
// lock.notify(); // 只唤醒一个
lock.notifyAll();// 全部唤醒,全部执行
}
}
}
wait / notify 的正确使用姿势(step1-5)
sleep(long n) 和 wait(long n) 的区别
1)sleep 是 Thread 的静态方法,wait 是 Object的方法
2)sleep 不需要强制和 synchronized 配合使用,但 wait 必须和 synchronized 联合使用,获取对象锁
3)如果 sleep 和 synchronized 联合使用,在 sleep 的时候不会释放对象锁,但 wait 在等待是会释放对象锁
4)sleep 和 wait 都不占用 CPU 时间,状态都是 TIMED_WAITING
public class BiasedDemo6 {
final static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (lock){
System.out.println("线程 t1 开始执行");
try {
// lock.wait();
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 t1 完成其他业务");
}
},"t1").start();
Thread.sleep(1000);
synchronized (lock){
System.out.println("主线程获得锁,开始执行");
}
}
}
/**
* STEP 0(使用 wait / notify 前)
**/
public class BiasedDemo7 {
final static Object room = new Object();
static boolean hasCigarette = false;
public static void main(String[] args) throws InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 线程[t1], 有没有烟?" +hasCigarette);
if (!hasCigarette){
System.out.println(sdf.format(new Date()) + " 线程[t1], 没有烟");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sdf.format(new Date()) + " 线程[t1], 有没有烟?" +hasCigarette);
if(hasCigarette){
System.out.println(sdf.format(new Date()) + " 线程[t1], 有烟,开始干活");
}
}
},"t1").start();
for (int i = 0; i < 5; i++) {
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 线程[其他],开始干活");
}
},"其他").start();
}
Thread.sleep(1000);
new Thread(()->{
// synchronized (room){
hasCigarette = true;
System.out.println(sdf.format(new Date()) + " 线程[送烟的],来送烟了");
// }
},"送烟的").start();
}
}
输出:(注意看前边的时间哈)
05:33:58 线程[t1], 有没有烟?false
05:33:58 线程[t1], 没有烟
05:33:59 线程[送烟的],来送烟了
05:34:00 线程[t1], 有没有烟?true
05:34:00 线程[t1], 有烟,开始干活
05:34:00 线程[其他],开始干活
05:34:00 线程[其他],开始干活
05:34:00 线程[其他],开始干活
05:34:00 线程[其他],开始干活
05:34:00 线程[其他],开始干活
- 其他干活的线程,都要一直阻塞,效率太低
- 线程 t1 必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
- 加了 synchronized(room) 后,就好比 t1 在里面反锁了门睡觉,烟根本没法送进门,main 线程没加 synchronized 就好像 main 线程是翻窗户进来的
- 解决方法:使用 wait - notify 机制
/**
* STEP 1(使用 wait / notify)
**/
public class BiasedDemo8 {
final static Object room = new Object();
static boolean hasCigarette = false;
public static void main(String[] args) throws InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 线程[t1], 有没有烟?" +hasCigarette);
if (!hasCigarette){
System.out.println(sdf.format(new Date()) + " 线程[t1], 没有烟");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sdf.format(new Date()) + " 线程[t1], 有没有烟?" +hasCigarette);
if(hasCigarette){
System.out.println(sdf.format(new Date()) + " 线程[t1], 有烟,开始干活");
}
}
},"t1").start();
for (int i = 0; i < 5; i++) {
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 线程[其他],开始干活");
}
},"其他").start();
}
Thread.sleep(1000);
new Thread(()->{
synchronized (room){
hasCigarette = true;
System.out.println(sdf.format(new Date()) + " 线程[送烟的],来送烟了");
room.notify();
}
},"送烟的").start();
}
}
输出
05:51:00 线程[t1], 有没有烟?false
05:51:00 线程[t1], 没有烟
05:51:00 线程[其他],开始干活
05:51:00 线程[其他],开始干活
05:51:00 线程[其他],开始干活
05:51:00 线程[其他],开始干活
05:51:00 线程[其他],开始干活
05:51:01 线程[送烟的],来送烟了
05:51:01 线程[t1], 有没有烟?true
05:51:01 线程[t1], 有烟,开始干活
- 解决了其他线程被阻塞的问题
- 但如果有其他线程也在等待条件呢?
虚假唤醒:
/**
* STEP 2(错误唤醒/虚假唤醒)
**/
public class BiasedDemo9 {
final static Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 线程[小南], 有没有烟?" +hasCigarette);
if (!hasCigarette){ //while (!hasCigarette){
System.out.println(sdf.format(new Date()) + " 线程[小南], 没有烟,等一会");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sdf.format(new Date()) + " 线程[小南], 有没有烟?" +hasCigarette);
if(hasCigarette){
System.out.println(sdf.format(new Date()) + " 线程[小南], 有烟,开始干活");
}
}
},"小南").start();
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 线程[小女], 有没有外卖?" +hasTakeout);
if (!hasTakeout){//while (!hasTakeout){
System.out.println(sdf.format(new Date()) + " 线程[小女], 没有外卖,等一会");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sdf.format(new Date()) + " 线程[小女], 有没有外卖?" +hasTakeout);
if(hasTakeout){
System.out.println(sdf.format(new Date()) + " 线程[小女], 有外卖,开始干活");
}
}
},"小女").start();
Thread.sleep(1000);
new Thread(()->{
synchronized (room){
hasTakeout = true;
System.out.println(sdf.format(new Date()) + " 线程[送货的],来送外卖了");
room.notify();
//room.notifyAll();
}
},"送货的").start();
}
}
输出:
06:02:46 线程[小南], 有没有烟?false
06:02:46 线程[小南], 没有烟,等一会
06:02:46 线程[小女], 有没有外卖?false
06:02:46 线程[小女], 没有外卖,等一会
06:02:47 线程[送货的],来送外卖了
06:02:47 线程[小女], 有没有外卖?true
06:02:47 线程[小女], 有外卖,开始干活
06:02:47 线程[小南], 有没有烟?false
//线程1
synchronized(room){
while(条件不成立){
room.wait();
}
// 干活
}
// 线程2
synchronized(room){
room.notifyAll();
}