何为虚假唤醒:

当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功;
比如买货:如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁;

避免虚假唤醒:

Synchronized版,生产者和消费者问题

  1. package com.jia.pc;
  2.  
  3. public class A {
  4.  
  5. public static void main(String[] args) {
  6.  
  7. Data data = new Data();
  8.  
  9. new Thread(()->{
  10. for (int i = 0; i < 10 ; i++) {
  11. try {
  12. data.increment();
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. },"A").start();
  18.  
  19. new Thread(()->{
  20. for (int i = 0; i < 10 ; i++) {
  21. try {
  22. data.decrement();
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. },"B").start();
  28.  
  29. new Thread(()->{
  30. for (int i = 0; i < 10 ; i++) {
  31. try {
  32. data.increment();
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. },"C").start();
  38.  
  39. new Thread(()->{
  40. for (int i = 0; i < 10 ; i++) {
  41. try {
  42. data.decrement();
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. }
  46. }
  47. },"D").start();
  48. }
  49. }
  50.  
  51. // 等待,业务,通知
  52. class Data{
  53.  
  54. private int number = 0;
  55.  
  56. // +1
  57. public synchronized void increment() throws InterruptedException {
  58. while (number != 0){
  59. // 等待
  60. this.wait();
  61. }
  62. number++;
  63. System.out.println(Thread.currentThread().getName() + "->" + number);
  64. // 通知其他线程,我+1完毕
  65. this.notifyAll();
  66. }
  67.  
  68. // -1
  69. public synchronized void decrement() throws InterruptedException {
  70. while (number == 0){
  71. // 等待
  72. this.wait();
  73. }
  74. number--;
  75. System.out.println(Thread.currentThread().getName() + "->" + number);
  76. // 通知其他线程,我-1完毕
  77. this.notifyAll();
  78. }
  79. }

运行结果:

  1. A->1
  2. B->0
  3. A->1
  4. B->0
  5. A->1
  6. B->0
  7. A->1
  8. B->0
  9. A->1
  10. B->0
  11. A->1
  12. B->0
  13. A->1
  14. B->0
  15. A->1
  16. B->0
  17. A->1
  18. B->0
  19. A->1
  20. B->0
  21. C->1
  22. D->0
  23. C->1
  24. D->0
  25. C->1
  26. D->0
  27. C->1
  28. D->0
  29. C->1
  30. D->0
  31. C->1
  32. D->0
  33. C->1
  34. D->0
  35. C->1
  36. D->0
  37. C->1
  38. D->0
  39. C->1
  40. D->0
  41.  
  42. Process finished with exit code 0

虚假幻想是如何产生的?

  把 while (number != 0) {}

  换成 if (number == 0) {}

  就会出现虚假唤醒。官方文档有标注;

为什么if判断会出现虚假唤醒?

  1. 因为if只会执行一次,执行完会接着向下执行if()外边的

  2. 而while不会,直到条件满足才会向下执行while()外边的

JUC版,生产者和消费者问题

使用 Condition 代码实现:

  1. package com.jia.pc;
  2.  
  3. import java.util.concurrent.locks.Condition;
  4. import java.util.concurrent.locks.Lock;
  5. import java.util.concurrent.locks.ReentrantLock;
  6.  
  7. public class C {
  8.  
  9. public static void main(String[] args) {
  10.  
  11. Data3 data = new Data3();
  12.  
  13. new Thread(()->{
  14. for (int i = 0; i < 10 ; i++) {
  15. data.printA();
  16. }
  17. },"A").start();
  18.  
  19. new Thread(()->{
  20. for (int i = 0; i < 10 ; i++) {
  21. data.printB();
  22. }
  23. },"B").start();
  24.  
  25. new Thread(()->{
  26. for (int i = 0; i < 10 ; i++) {
  27. data.printC();
  28. }
  29. },"C").start();
  30. }
  31. }
  32.  
  33. // 资源类
  34. class Data3{
  35.  
  36. private Lock lock = new ReentrantLock();
  37.  
  38. Condition conditionA = lock.newCondition();
  39. Condition conditionB = lock.newCondition();
  40. Condition conditionC = lock.newCondition();
  41.  
  42. private int number = 1;
  43.  
  44. public void printA(){
  45. lock.lock();
  46. try {
  47. while (number != 1){
  48. //等待
  49. conditionA.await();
  50. }
  51. System.out.println(Thread.currentThread().getName()+"->"+"AAAAA");
  52. //唤醒执行的线程 B
  53. number = 2;
  54. conditionB.signal();
  55. } catch (Exception e) {
  56. e.printStackTrace();
  57. }finally {
  58. lock.unlock();
  59. }
  60. }
  61.  
  62. public void printB(){
  63. lock.lock();
  64. try {
  65. while (number != 2){
  66. //等待
  67. conditionB.await();
  68. }
  69. System.out.println(Thread.currentThread().getName()+"->"+"BBBBB");
  70. //唤醒 C
  71. number = 3;
  72. conditionC.signal();
  73. } catch (Exception e) {
  74. e.printStackTrace();
  75. }finally {
  76. lock.unlock();
  77. }
  78. }
  79.  
  80. public void printC(){
  81. lock.lock();
  82. try {
  83. while (number != 3){
  84. //等待
  85. conditionC.await();
  86. }
  87. System.out.println(Thread.currentThread().getName()+"->"+"CCCCC");
  88. //唤醒 A
  89. number = 1;
  90. conditionA.signal();
  91. } catch (Exception e) {
  92. e.printStackTrace();
  93. }finally {
  94. lock.unlock();
  95. }
  96. }
  97. }

Condition:它可以精准的通知和唤醒线程;

 

Java并发编程虚假唤醒问题(生产者和消费者关系)的更多相关文章

  1. JAVA并发框架之Semaphore实现生产者与消费者模型

    分类: Java技术      锁和信号量(Semaphore)是实现多线程同步的两种常用的手段.信号量需要初始化一个许可值,许可值可以大于0,也可以小于0,也可以等于0.      如果大于0,表示 ...

  2. Java并发编程之美之并发编程线程基础

    什么是线程 进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程至少有一个线程,进程的多个线程共享进程的资源. java启动main函数其实就 ...

  3. 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport

    在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...

  4. Java 并发编程 生产者消费者模式

    本文部分摘自<Java 并发编程的艺术> 模式概述 在线程的世界里,生产者就是生产数据的线程,消费者就是消费数据的数据.生产者和消费者彼此之间不直接通信,而是通过阻塞队列进行通信,所以生产 ...

  5. java并发编程之美-阅读记录1

    1.1什么是线程? 在理解线程之前先要明白什么是进程,因为线程是进程中的一个实体.(线程是不会独立存在的) 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程中的 ...

  6. Java并发编程(您不知道的线程池操作), 最受欢迎的 8 位 Java 大师,Java并发包中的同步队列SynchronousQueue实现原理

    Java_并发编程培训 java并发程序设计教程 JUC Exchanger 一.概述 Exchanger 可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchan ...

  7. Java并发编程--基础进阶高级(完结)

    Java并发编程--基础进阶高级完整笔记. 这都不知道是第几次刷狂神的JUC并发编程了,从第一次的迷茫到现在比较清晰,算是个大进步了,之前JUC笔记不见了,重新做一套笔记. 参考链接:https:// ...

  8. JAVA并发编程J.U.C学习总结

    前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...

  9. Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

随机推荐

  1. Linux基础:子网划分

    一.ip地址基本知识 1.1 ip地址的结构和分类 1.2 特殊ip地址 1.3 子网掩码 1.4 ip地址申请 二.子网划分 2.1 子网划分概念 2.2 c类子网划分初探 2.3 子网划分步骤 2 ...

  2. 协程 & IO模型 & HTTP协议

    今日内容 进程池与线程池的基本使用 协程理论与实操 IO模型 前端简介 内容详细 一.进程池与线程池的基本使用 1.进程池与线程池的作用 为了保证计算机硬件安全的前提下,提升程序的运行效率 2.回调机 ...

  3. Solution -「CF 1392H」ZS Shuffles Cards

    \(\mathcal{Description}\)   Link.   打乱的 \(n\) 张编号 \(1\sim n\) 的数字排和 \(m\) 张鬼牌.随机抽牌,若抽到数字,将数字加入集合 \(S ...

  4. VS2019下配置OpenGL全过程

    一:下载VS2019 官网下载社区版 二:下载GLEW.GLFW 百度网盘地址: 链接:https://pan.baidu.com/s/1Uvz9svdnVRvDXNHjVgApig 提取码:rsgp ...

  5. 私有化轻量级持续集成部署方案--07-私有NPM仓库-Verdaccio

    提示:本系列笔记全部存在于 Github, 可以直接在 Github 查看全部笔记 对于个人来说,私有NPM仓库 作用性基本很小,但是对于企业,私有NPM仓库 可以保护代码暴露,具有很大的意义. 也是 ...

  6. Docker配置Pytorch深度学习环境

    拉取镜像 $ docker pull pytorch/pytorch:1.9.1-cuda11.1-cudnn8-devel 查看本地已有镜像 $ docker images 创建容器 $ docke ...

  7. Vue 源码解读(7)—— Hook Event

    前言 Hook Event(钩子事件)相信很多 Vue 开发者都没有使用过,甚至没听过,毕竟 Vue 官方文档中也没有提及. Vue 提供了一些生命周期钩子函数,供开发者在特定的逻辑点添加额外的处理逻 ...

  8. 【C# Parallel】开端

    使用条件 1.必须熟练掌握锁.死锁.task的知识,他是建立这两个的基础上的.task建立在线程和线程池上的. 2.并不是所有代码都适合并行化. 例如,如果某个循环在每次迭代时只执行少量工作,或它在很 ...

  9. 【.net】AppDoamin| 应用程序域

    在.net framework框架上,一个进程可以有多个appdomain.因此一个进程可以运行多个程序. 应用程序域的出现: (来自msdn)    在.net出现以前,一个进程下,只能运行一个应用 ...

  10. linux下通过脚本方式对中间件weblogic进行补丁升级

    转至:http://bbs.learnfuture.com/topic/48 linux下通过脚本方式对中间件weblogic进行补丁升级 刘五奎 [摘要] 在运维行业,系统或软件漏洞每每牵动着每一个 ...