4.12多线程--wait/notify

故事引入

image.png

image.png

wait / notify 原理

image.png
  • 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();
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 229,237评论 6 537
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,957评论 3 423
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 177,248评论 0 382
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,356评论 1 316
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,081评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,485评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,534评论 3 444
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,720评论 0 289
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,263评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,025评论 3 356
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,204评论 1 371
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,787评论 5 362
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,461评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,874评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,105评论 1 289
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,945评论 3 395
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,205评论 2 375

推荐阅读更多精彩内容