开始之前先看看,sleep(long n)wait(long n) 的区别:

1) sleep 是 Thread 的静态方法,而 wait 是 Object 的方法

2) sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用

3) sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁

4) 它们状态 TIMED_WAITING

建议:锁对象加final修饰,这样锁对象不可变。

改进 1

static final Object room = new Object();
static boolean hasCigarette = false;  // 是否有烟
static boolean hasTakeout = false;

思考下面的解决方案好不好,为什么?

new Thread(() -> {
   synchronized (room) {
       log.debug("有烟没?[{}]", hasCigarette);
       if (!hasCigarette) {
           log.debug("没烟,先歇会!");
           sleep(2);
      }
       log.debug("有烟没?[{}]", hasCigarette);
       if (hasCigarette) {
           log.debug("可以开始干活了");
      }
  }
}, "小南").start();

for (int i = 0; i < 5; i++) {
   new Thread(() -> {
       synchronized (room) {
           log.debug("可以开始干活了");
      }
  }, "其它人").start();
}

sleep(1);
new Thread(() -> {
   // 这里能不能加 synchronized (room)? 不能,sleep不会释放对象锁
   hasCigarette = true;
   log.debug("烟到了噢!");
}, "送烟的").start();

输出

20:49:49.883 [小南] c.TestCorrectPosture - 有烟没?[false]
20:49:49.887 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:49:50.882 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:49:51.887 [小南] c.TestCorrectPosture - 有烟没?[true]
20:49:51.887 [小南] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
  • 其它干活的线程,都要一直阻塞,效率太低

  • 小南线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来

  • 加了 synchronized (room) 后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main 没加 synchronized 就好像 main 线程是翻窗户进来的

  • 解决方法,使用 wait - notify 机制

改进 2

思考下面的实现行吗,为什么?

new Thread(() -> {
   synchronized (room) {
       log.debug("有烟没?[{}]", hasCigarette);
       if (!hasCigarette) {
           log.debug("没烟,先歇会!");
           try {
               room.wait(2000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       log.debug("有烟没?[{}]", hasCigarette);
       if (hasCigarette) {
           log.debug("可以开始干活了");
      }
  }
}, "小南").start();

for (int i = 0; i < 5; i++) {
   new Thread(() -> {
       synchronized (room) {
           log.debug("可以开始干活了");
      }
  }, "其它人").start();
}

sleep(1);
new Thread(() -> {
   synchronized (room) {
       hasCigarette = true;
       log.debug("烟到了噢!");
       room.notify();
  }
}, "送烟的").start();

输出

20:51:42.489 [小南] c.TestCorrectPosture - 有烟没?[false]
20:51:42.493 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:43.490 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:51:43.490 [小南] c.TestCorrectPosture - 有烟没?[true]
20:51:43.490 [小南] c.TestCorrectPosture - 可以开始干活了
  • 解决了其它干活的线程阻塞的问题

  • 但如果有其它线程也在等待条件呢?

改进 3

new Thread(() -> {
   synchronized (room) {
       log.debug("有烟没?[{}]", hasCigarette);
       if (!hasCigarette) {
           log.debug("没烟,先歇会!");
           try {
               room.wait();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       log.debug("有烟没?[{}]", hasCigarette);
       if (hasCigarette) {
           log.debug("可以开始干活了");
      } else {
           log.debug("没干成活...");
      }
  }
}, "小南").start();

new Thread(() -> {
   synchronized (room) {
       Thread thread = Thread.currentThread();
       log.debug("外卖送到没?[{}]", hasTakeout);
       if (!hasTakeout) {
           log.debug("没外卖,先歇会!");
           try {
               room.wait();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       log.debug("外卖送到没?[{}]", hasTakeout);
       if (hasTakeout) {
           log.debug("可以开始干活了");
      } else {
           log.debug("没干成活...");
      }
  }
}, "小女").start();

sleep(1);
new Thread(() -> {
   synchronized (room) {
       hasTakeout = true;
       log.debug("外卖到了噢!");
       room.notify();
  }
}, "送外卖的").start();

输出

20:53:12.173 [小南] c.TestCorrectPosture - 有烟没?[false]
20:53:12.176 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:53:12.176 [小女] c.TestCorrectPosture - 外卖送到没?[false]
20:53:12.176 [小女] c.TestCorrectPosture - 没外卖,先歇会!
20:53:13.174 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
20:53:13.174 [小南] c.TestCorrectPosture - 有烟没?[false]
20:53:13.174 [小南] c.TestCorrectPosture - 没干成活...
  • notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】

  • 解决方法,改为 notifyAll

改进 4

new Thread(() -> {
   synchronized (room) {
       hasTakeout = true;
       log.debug("外卖到了噢!");
       room.notifyAll();
  }
}, "送外卖的").start();

输出

20:55:23.978 [小南] c.TestCorrectPosture - 有烟没?[false]
20:55:23.982 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:55:23.982 [小女] c.TestCorrectPosture - 外卖送到没?[false]
20:55:23.982 [小女] c.TestCorrectPosture - 没外卖,先歇会!
20:55:24.979 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
20:55:24.979 [小女] c.TestCorrectPosture - 外卖送到没?[true]
20:55:24.980 [小女] c.TestCorrectPosture - 可以开始干活了
20:55:24.980 [小南] c.TestCorrectPosture - 有烟没?[false]
20:55:24.980 [小南] c.TestCorrectPosture - 没干成活...
  • 用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了

  • 解决方法,用 while + wait,当条件不成立,再次 wait

改进 5

将 if 改为 while

if (!hasCigarette) {
   log.debug("没烟,先歇会!");
   try {
       room.wait();
  } catch (InterruptedException e) {
       e.printStackTrace();
  }
}

改动后

while (!hasCigarette) {
   log.debug("没烟,先歇会!");
   try {
       room.wait();
  } catch (InterruptedException e) {
       e.printStackTrace();
  }
}

输出

20:58:34.322 [小南] c.TestCorrectPosture - 有烟没?[false]
20:58:34.326 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:58:34.326 [小女] c.TestCorrectPosture - 外卖送到没?[false]
20:58:34.326 [小女] c.TestCorrectPosture - 没外卖,先歇会!
20:58:35.323 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
20:58:35.324 [小女] c.TestCorrectPosture - 外卖送到没?[true]
20:58:35.324 [小女] c.TestCorrectPosture - 可以开始干活了
20:58:35.324 [小南] c.TestCorrectPosture - 没烟,先歇会!

while循环 + wait 防止虚假唤醒,并且一般使用notifyAll来进行唤醒

synchronized(lock) {
   while(条件不成立) {
       lock.wait();
  }
   // 干活
}

//另一个线程
synchronized(lock) {
   lock.notifyAll();
}

Java并发(二十二)----wait notify的正确姿势的更多相关文章

  1. Java并发(十二):CAS Unsafe Atomic

    一.Unsafe Java无法直接访问底层操作系统,而是通过本地(native)方法来访问.不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作. 这个类尽管 ...

  2. JAVA基础知识总结:一到二十二全部总结

    >一: 一.软件开发的常识 1.什么是软件? 一系列按照特定顺序组织起来的计算机数据或者指令 常见的软件: 系统软件:Windows\Mac OS \Linux 应用软件:QQ,一系列的播放器( ...

  3. Java设计模式(十二) 策略模式

    原创文章,同步发自作者个人博客,http://www.jasongj.com/design_pattern/strategy/ 策略模式介绍 策略模式定义 策略模式(Strategy Pattern) ...

  4. 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条

    http://blog.csdn.net/terryzero/article/details/3797782 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条 标签: swing编程 ...

  5. Java进阶(三十二) HttpClient使用详解

    Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们 ...

  6. JAVA之旅(二十二)——Map概述,子类对象特点,共性方法,keySet,entrySet,Map小练习

    JAVA之旅(二十二)--Map概述,子类对象特点,共性方法,keySet,entrySet,Map小练习 继续坚持下去吧,各位骚年们! 事实上,我们的数据结构,只剩下这个Map的知识点了,平时开发中 ...

  7. Java进阶(五十二)利用LOG4J生成服务日志

    Java进阶(五十二)利用LOG4J生成服务日志 前言 由于论文写作需求,需要进行流程挖掘.前提是需要有真实的事件日志数据.真实的事件日志数据可以用来发现.监控和提升业务流程. 为了获得真实的事件日志 ...

  8. [Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors

    [Java并发编程(二)] 线程池 FixedThreadPool.CachedThreadPool.ForkJoinPool?为后台任务选择合适的 Java executors ... 摘要 Jav ...

  9. “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  10. “全栈2019”Java多线程第十二章:后台线程setDaemon()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

随机推荐

  1. Python基础——数字类型int与float、字符串、列表、元组、字典、集合、可变类型与不可变类型、数据类型总结

    文章目录 一 引子 二 数字类型int与float 2.1 定义 2.2 类型转换 2.3 使用 三 字符串 3.1 定义: 3.2 类型转换 3.3 使用 3.3.1 优先掌握的操作 3.3.2 需 ...

  2. CCF CSP认证注册、报名、查询成绩、做模拟题等答疑

    CCF CSP认证注册.报名.查询成绩.做模拟题等答疑 CCF CSP认证中心将考生在注册,或报名,或查询成绩,或历次真题练习时遇到的问题进行汇总,并给出解决方法,具体如下: 1.注册时,姓名可否随意 ...

  3. 11g GI监听测试增加其他本地端口

    11.2 GI中监听器的地址和端口信息被移到了 endpoints_listener.ora中. 使用 endpoints_listener.ora的情况下不应使用lsnrctl管理LISTENER, ...

  4. 利用ChatGPT提升测试工作效率——测试工程师的新利器(一)

    1.前言 随着ChatGPT的爆火,各个行业开始尝试利用ChatGPT来提升工作效率.其中,测试工程师们也开始探索如何应用ChatGPT来加强测试工作.在本文中,我们将从测试工程师的角度出发,探讨Ch ...

  5. 03-11gR2单机通过RMAN恢复到RAC(未验证)

    1.在单机上做一个完全备份,并将备份集拷贝到RAC的第一个节点上. 2.强行启动到nomount 3.恢复spfile 4.创建pfile,修改pfile,重建spfile #####修改contro ...

  6. Hugging Face 分词器新增聊天模板属性

    一个幽灵,格式不正确的幽灵,在聊天模型中游荡! 太长不看版 现存的聊天模型使用的训练数据格式各各不同,我们需要用这些格式将对话转换为单个字符串并传给分词器.如果我们在微调或推理时使用的格式与模型训练时 ...

  7. PTA乙级1039(C++)散列表解法

    题目 1039 到底买不买  小红想买些珠子做一串自己喜欢的珠串.卖珠子的摊主有很多串五颜六色的珠串,但是不肯把任何一串拆散了卖. 于是小红要你帮忙判断一下,某串珠子里是否包含了全部自己想要的珠子?如 ...

  8. Qt5 学习积累

    目录 1.cout/cin 2.随机数 3.QSting. string.QChar,.char等的转换 4.退出 5.Qt::tr() 6.QFrame::shape,shadow 7.QCombo ...

  9. Go语言基准测试(benchmark)三部曲之二:内存篇

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<Go语言基准测试(benchm ...

  10. [Python急救站课程]凯撒密码加密与解密

    密码的解密是一个有趣的过程,凯撒密码也是一个较为简单的密码,是通过位移来解决的. 当我们把凯撒密码位移量设置为3时就可以用Python做出以下程序. 加密程序: plaincode = input(& ...