38.等待唤醒机制

一、线程间通信

概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不同,比如著名的生产者消费者模型

注意:sleep不放弃锁,wait会放弃锁

二、等待唤醒机制【合作关系】

线程之间的关系:

  1. 竞争:争夺锁。
  2. 协作:一个线程进行了规定操作后,就进入等待状态wait(), 等待其他线程执行完他们的指定代码过后 再将其唤醒notify();在有多个线程进行等待时, 如果需要,可以使用notifyAll()来唤醒所有的等待线程

wait/notify 就是线程间的一种协作机制

等待唤醒中的方法:等待唤醒机制就是用于解决线程间通信的问题

  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
  2. notify:【唤醒的线程的随机的】则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

notice:哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
总结如下:
- 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
- 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. 锁对象可以是任意对象.wait方法与notify方法是属于Object类的方法的。因为:而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

容易导致的错误理解--经过多次试验,发现,等待唤醒机制只使用于一个生产者和一个消费者这种模型关系;如果是类似于一个生产者和多个消费者,可能会发生多个消费者的wait操作会相互干扰;比如,一个生产者a,两个消费者b,c,当一个消费者b正在wait等待时,锁被释放,却被另一个消费者c拿到(逻辑上应该让生产者拿到锁进行操作),而此时消费者c也进入了wait状态,导致消费者b的wait逻辑上异常停止。程序出现了BUG!**

正确的理解:notify唤醒的wait是随机的,由于消费者最后都会notify,但是唤醒的可能是另一个正在wait的而不该被wait的消费者而产生了错觉!

如下演示:

  1. 产品:
public class Goods {
    boolean exist = false;
}
  1. 生产者
public class Producer implements Runnable {
    Goods goods;
    public Producer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while(true) {

            // 故意不让生产者很容易抢到锁
            try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }

            synchronized (goods){
                if(goods.exist){
                    try {  goods.wait(); // 注意notify的随机叫醒。
                    } catch (InterruptedException e) { e.printStackTrace(); }
                }
                System.out.println(Thread.currentThread().getName() + "生产了。");
                goods.exist = true;
                goods.notify();
            }
        }
    }
}
  1. 消费者
public class Consumer implements Runnable{
    Goods goods;
    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while(true) {

            synchronized (goods){
                if(!goods.exist){
                    try {
                        System.out.println(Thread.currentThread().getName() + "拿到锁了。");
                        goods.wait();
                        System.out.println(Thread.currentThread().getName() + "释放锁了");
//                        if(!goods.exist) {
//                            goods.notify();
//                            continue;
//                        }
                    } catch (InterruptedException e) { e.printStackTrace(); }
                }
                try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.print(Thread.currentThread().getName() + "消费了。");
                System.out.println("逻辑上,exist应该是 true,而实际值 = " + goods.exist);
                goods.exist = false;
                goods.notify();
            }
        }
    }
}
  1. 测试函数
public class Main {

    public static void main(String[] args) {

        Goods goods = new Goods();

        Producer producer = new Producer(goods);
        Consumer consumer = new Consumer(goods);

        new Thread(producer,"--生产者  ").start();
        new Thread(consumer,"@@消费者 1").start();
        new Thread(consumer,"@@消费者 2").start();
        new Thread(consumer,"@@消费者 3").start();
        new Thread(consumer,"@@消费者 4").start();
        new Thread(consumer,"@@消费者 5").start();
        new Thread(consumer,"@@消费者 6").start();
    }
}
  1. 运行结果(一个消费者导致另一个消费者非正常唤醒)
@@消费者 1拿到锁了。
@@消费者 6拿到锁了。
@@消费者 5拿到锁了。
@@消费者 4拿到锁了。
@@消费者 3拿到锁了。
@@消费者 2拿到锁了。
--生产者  生产了。
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = true
@@消费者 1拿到锁了。
@@消费者 6释放锁了
@@消费者 6消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 5释放锁了
@@消费者 5消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 5拿到锁了。
@@消费者 4释放锁了
@@消费者 4消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 3释放锁了
@@消费者 3消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 2释放锁了
@@消费者 2消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 5释放锁了
@@消费者 5消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1拿到锁了。
@@消费者 2拿到锁了。
@@消费者 3拿到锁了。
--生产者  生产了。
@@消费者 4消费了。逻辑上,exist应该是 true,而实际值 = true
@@消费者 6拿到锁了。
@@消费者 2释放锁了
@@消费者 2消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 5拿到锁了。
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1拿到锁了。
@@消费者 6释放锁了
@@消费者 6消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 6拿到锁了。
@@消费者 5释放锁了
@@消费者 5消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1拿到锁了。
@@消费者 6释放锁了
@@消费者 6消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 6拿到锁了。
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1拿到锁了。
@@消费者 6释放锁了
@@消费者 6消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 6拿到锁了。
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1拿到锁了。
@@消费者 6释放锁了

Process finished with exit code -1

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

推荐阅读更多精彩内容

  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 11,263评论 4 56
  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 2,122评论 0 14
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,108评论 0 23
  • 一、基础知识:1、JVM、JRE和JDK的区别:JVM(Java Virtual Machine):java虚拟机...
    杀小贼阅读 2,379评论 0 4
  • 北京,今天大风,同样是蓝天。清晨,确切说是6点多,我醒来,不是因为是睡得早,可能是年龄大了,也可能是习惯早起,总在...
    FreeManFree阅读 354评论 0 0