文章目录

  • 概述
  • 场景
  • 引子
  • synchronized wait/notify机制
  • synchronized wait/notify 改造
  • 问题

概述

Java中线程通信协作的最常见的两种方式:

  • syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
  • ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

线程间直接的数据交换:

  • 通过管道进行线程间通信:1)字节流;2)字符流

可参考: Java多线程编程核心技术


场景

场景假设:

一个工作台,两个工人: Worker A 和 Workder B .

约定,Worker A 生产货物到工作台上, Workder B 从工作台 取走(消费)货物。

  • 当 工作台上没有货物时,Worker A 才生产货物,否则等待Worker B 取走(消费)货物。
  • 当 工作台上有货物时, Woker B 才从工作台取走(消费)货物,否则等待Worker A 生产货物

引子

我们先来看下线程之间不通信的情况 (错误示例)

package com.artisan.test;public class ProduceConsumeWrongDemo {// 锁private final Object LOCK = new Object();// 模拟多线程间需要通信的数据  iprivate int i = 0 ;public void produce() throws InterruptedException {// 加锁synchronized (LOCK){System.out.println("produce:" + i++);Thread.sleep(1_000);}}public void consume() throws InterruptedException{// 加锁synchronized (LOCK){System.out.println("consume:" + i);Thread.sleep(1_000);}}public static void main(String[] args) throws InterruptedException{ProduceConsumeWrongDemo pc = new ProduceConsumeWrongDemo();// 生产线程new Thread(()->{while (true){try {pc.produce();} catch (InterruptedException e) {e.printStackTrace();}}}).start();// 消费线程new Thread(()->{while (true){try {pc.consume();} catch (InterruptedException e) {e.printStackTrace();}}}).start();}
}

运行结果:

"E:\Program Files\Java\jdk1.8.0_161\bin\java" "-javaagent:E:\Program Files\JetBrains\IntelliJ IDEA 2017.2.4\lib\idea_rt.jar=52137:E:\Program Files\JetBrains\IntelliJ IDEA 2017.2.4\bin" -Dfile.encoding=UTF-8 -classpath "E:\Program Files\Java\jdk1.8.0_161\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\rt.jar;D:\IdeaProjects\mvc\target\classes" com.artisan.test.ProduceConsumeWrongDemo
produce:0
produce:1
consume:2
consume:2
consume:2
produce:2
consume:3
consume:3
consume:3
produce:3
produce:4
produce:5
consume:6
....
....
....
....
....
....
....

很明显的可以看到,数据都是错乱的,因为没有线程间的通信,全凭CPU调度,生产线程和消费线程都很随意,数据一团糟糕,那该如何改进呢?


synchronized wait/notify机制

  • wait()——让当前线程 (Thread.concurrentThread()
    方法所返回的线程) 释放对象锁并进入等待(阻塞)状态。
  • notify()——唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。
  • notifyAll()——唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。

为了解决上面的问题,我们先来了解下synchronized wait/notify .

  • wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

  • 调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁). 因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。如果当前线程没有这个对象的锁就调用wait()方法,则会抛出IllegalMonitorStateException.

  • 调用某个对象的wait()方法,相当于让当前线程交出(释放)此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁

  • 调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程. 同样的,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

  • 调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程

  • notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。

举个例子: 假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的monitor,此时Thread4拥有对象objectA的monitor,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3只有一个能被唤醒。

注意,被唤醒不等于立刻就获取了objectA的monitor。

假若在Thread4中调用objectA.notifyAll()方法,则Thread1、Thread2和Thread3三个线程都会被唤醒,至于哪个线程接下来能够获取到objectA的monitor就具体依赖于操作系统的调度了。

一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。


synchronized wait/notify 改造

package com.artisan.test;public class ProduceConsumerDemo {// 对象监视器-锁private final Object LOCK = new Object();// 是否生产出数据的标识private boolean isProduced = false;// volatile 确保可见性, 假设 i 就是生产者生产的数据private volatile int i = 0 ;public  void produce(){// 加锁synchronized (LOCK){if (isProduced){try {// 让当前线程 (Thread.concurrentThread() 方法所返回的线程) 释放对象锁并进入等待(阻塞)状态// 如果已经生产,则等待LOCK.wait();} catch (InterruptedException e) {e.printStackTrace();}}else{// 生产数据i++;System.out.println("Produce:" + i);// 唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行// 通知等待的Worker B 来消费数据LOCK.notify();// 将生产标识置为trueisProduced = true;}}}public void consume(){// 加锁synchronized (LOCK){if (isProduced){// 消费数据System.out.println("Consume:" + i);// 唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行// 通知 等待的Wokrer A 生产数据LOCK.notify();// 已经消费完了,将生产标识置为falseisProduced = false;}else{try {// 让当前线程 (Thread.concurrentThread() 方法所返回的线程) 释放对象锁并进入等待(阻塞)状态// 未生产,Worker B等待LOCK.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {ProduceConsumerDemo produceConsumerDemo = new ProduceConsumerDemo();new Thread(){@Overridepublic void run() {while(true) produceConsumerDemo.produce();}}.start();new Thread(){@Overridepublic void run() {while(true) produceConsumerDemo.consume();}}.start();}
}


当然了并不是绝对的上面的对应关系(这里只是为了演示),因为notify唤醒后,线程只是进入Runnable状态,至于哪个线程能进入到running状态,就看哪个线程能抢到CPU的资源了。 JVM规范并没有规定哪个线程优先得到执行权,每个JVM的实现都是不同的


单个生产者 单个消费者,运行OK

.....
.....
.....Produce:1171
Consume:1171
Produce:1172
Consume:1172
Produce:1173
Consume:1173
Produce:1174
Consume:1174
Produce:1175
Consume:1175
Produce:1176
Consume:1176.....
.....
.....

问题

单个生产者 单个消费者 上面的代码是没有问题的,加入有多个生产者 和多个消费者呢?

我们来复用上面的代码来演示下 ,其他代码保持不变,仅在main方法中改造下,两个生产者,两个消费者

  Stream.of("P1","P2").forEach(n-> new Thread(){@Overridepublic void run() {while(true) produceConsumerDemo.produce();}}.start());Stream.of("C1","C2").forEach(n->new Thread(){@Overridepublic void run() {while(true) produceConsumerDemo.consume();}}.start());

下篇博客,我们来分析下原因,并给出解决办法


高并发编程-线程通信_使用wait和notify进行线程间的通信相关推荐

  1. 高并发编程-线程通信_使用wait和notify进行线程间的通信2_多生产者多消费者导致程序假死原因分析

    文章目录 概述 jstack或者可视化工具检测是否死锁(没有) 原因分析 概述 高并发编程-线程通信_使用wait和notify进行线程间的通信 - 遗留问题 我们看到了 应用卡住了 .... 怀疑是 ...

  2. 高并发编程-使用wait和notifyAll进行线程间的通信3_多线程下的生产者消费者模型和notifyAll

    文章目录 概述 解决办法 概述 高并发编程-线程通信_使用wait和notify进行线程间的通信2_多生产者多消费者导致程序假死原因分析 中分析了假死的原因,这里我们来看下改如何解决在多线程下出现的这 ...

  3. java线程高并发编程

    java线程详解及高并发编程庖丁解牛 线程概述: 祖宗: 说起java高并发编程,就不得不提起一位老先生Doug Lea,这位老先生可不得了,看看百度百科对他的评价,一点也不为过: 如果IT的历史,是 ...

  4. libevent c++高并发网络编程_高并发编程学习(2)——线程通信详解

    前序文章 高并发编程学习(1)--并发基础 - https://www.wmyskxz.com/2019/11/26/gao-bing-fa-bian-cheng-xue-xi-1-bing-fa-j ...

  5. 高并发编程学习(2)——线程通信详解

    前序文章 高并发编程学习(1)--并发基础 - https://www.wmyskxz.com/2019/11/26/gao-bing-fa-bian-cheng-xue-xi-1-bing-fa-j ...

  6. 高并发编程_高并发编程系列:7大并发容器详解(附面试题和企业编程指南)...

    不知道从什么时候起,在Java编程中,经常听到Java集合类,同步容器.并发容器,高并发编程成为当下程序员需要去了解掌握的技术之一,那么他们有哪些具体分类,以及各自之间的区别和优劣呢? 只有把这些梳理 ...

  7. 高并发编程-自定义简易的线程池(2),体会原理

    文章目录 概述 示例 概述 高并发编程-自定义简易的线程池(1),体会原理 中只实现了任务队列,我们这里把其余的几个也补充进来 拒绝策略 关闭线程池 最小 最大 活动线程数 - 示例 比较简单,直接上 ...

  8. 高并发编程-Thread_正确关闭线程的三种方式

    文章目录 概述 stop() Deprecated 方式一 设置开关 方式二 调用interrupt API 方式三 暴力结束线程-> Daemon Thread + interrupt API ...

  9. 高并发编程_高并发编程系列:全面剖析Java并发编程之AQS的核心实现

    在并发编程领域,AQS号称是并发同步组件的基石,很多并发同步组件都是基于AQS实现,所以想掌握好高并发编程,你需要掌握好AQS. 本篇主要通过对AQS的实现原理.数据模型.资源共享方式.获取锁的过程, ...

最新文章

  1. mysql的存储引擎详解_Mysql存储引擎详解
  2. Framework 动态库加载 xib
  3. leetcode刷题实录:2
  4. 穷人迈向富翁的理财十步曲
  5. matlab将矩阵分解成lu,10行代码实现矩阵的LU分解(matlab)
  6. xcom2.0_发布Xcom 2,Elliot Quest,Mesa图形库以及更多开放式游戏新闻
  7. Python(十九):比较、深浅拷贝
  8. 国内外组态软件对比分析(InTouch、WinCC、iFix、iNeuOS)
  9. PMP 考点 第十章 项目沟通管理
  10. PS照片处理尺寸参考表
  11. 关于回溯模型的两种解空间树
  12. 一个屌丝程序猿的人生(三)
  13. tensorflow:Not creating XLA devices, tf_xla_enable_xla_devices not set
  14. 使用QT5书写的护眼程序
  15. 实用解析dmp文件内容
  16. 阿里云ECS端口无法访问问题解决
  17. 干货 | 在线查你的个人数据有没有泄露
  18. 羊驼alpaca php,“草泥马”-----羊驼(Alpaca)
  19. “MacTalk 跨越边界” iBooks.
  20. mysql计算两个日期间的工作时长(参数传入每天上班时间,并剔除周末)

热门文章

  1. php编程查错,盘点PHP编程常见失误
  2. Linux下C语言程序的内存布局(内存模型)
  3. linux 解包与打包
  4. elasticsearch python API
  5. 168. Leetcode 134. 加油站 (贪心算法-模拟题目)
  6. 打印两个有序链表的公共部分
  7. pandas 笔记:合并操作
  8. 增强学习(二)----- 马尔可夫决策过程MDP
  9. Python入门100题 | 第025题
  10. QT中使用QCustomplot设置坐标原点在左上或者反转镜像坐标轴