开始之前先看看,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. 聊聊基于Alink库的决策树模型算法实现

    示例代码及相关内容来源于<Alink权威指南(Java版)> 概述 决策树模型再现了人们做决策的过程,该过程由一系列的判断构成,后面的判断基于前面的判断结果,不断缩小范围,最终推出结果. ...

  2. .NET应用如何防止被反编译

    前言 前段时间分享了两篇关于.NET反编译相关的文章,然后文章留言区就有小伙伴提问:如何防止被反编译?因此本篇文章我们就来讲讲.NET应用如何防止被反编译..NET反编译相关的文章可以看如下文章: 4 ...

  3. 【RocketMQ】Dledger模式下的日志复制

    RocketMQ在开启Dledger时,使用DLedgerCommitLog,其他情况使用的是CommitLog来管理消息的存储.在Dledger模式下,消息写入时Leader节点还需要将消息转发给F ...

  4. 【PHP正则表达式】

    [PHP正则表达式] 最近写题总是遇到php正则表达式的匹配函数,于是进行一个总结. 1.什么是正则表达式 是php在进行搜索时用于匹配的模式字符串.一般用于php对特定字符序列的替换和搜索. 2.正 ...

  5. tunm二进制协议在python上的实现

    tunm二进制协议在python上的实现 tunm是一种对标JSON的二进制协议, 支持JSON的所有类型的动态组合 支持的数据类型 基本支持的类型 "u8", "i8& ...

  6. nginx防盗链+flv.js拉流地址防盗

    需要此版本nginx的私信留邮箱,支持http-flv,支持rtmp,支持secure_link nginx.conf配置: c#后端生成链接 结果:

  7. 01背包问题的js解决方式

    如果你有兴趣看这个相信你已经对背包问题有所了解,所以关于背包问题的描述,我就不写了. 只记录一下自己对这个问题的一些看法和思考,于我而言,这个东西现在困扰我的是如何确定最优解. 实质上关于背包问题网上 ...

  8. [Python急救站课程]动态爱心绘画

    想不想画一个动态爱心来哄女朋友高兴呢? 那么它来啦 import random from math import sin, cos, pi, log from tkinter import * CAN ...

  9. 【Vue3响应式原理#02】Proxy and Reflect

    专栏分享:vue2源码专栏,vue3源码专栏,vue router源码专栏,玩具项目专栏,硬核推荐 欢迎各位ITer关注点赞收藏 背景 以下是柏成根据Vue3官方课程整理的响应式书面文档 - 第二节, ...

  10. [AI]人工智能早就可以拥有有大量的初级意识

    引子 意识是人类最基本而神秘的经验之一.在探索意识的本质时,我们需要建立清晰的概念分类体系,以免将它混同于其他概念而无法深入研究. 本文旨在阐述人类意识可能包含的两个层面:初级意识和高级意识,并明确区 ...