本文源码:GitHub·点这里 || GitEE·点这里

一、概念简介

1、线程通信

在操作系统中,线程是个独立的个体,但是在线程执行过程中,如果处理同一个业务逻辑,可能会产生资源争抢,导致并发问题,通常使用互斥锁来控制该逻辑。但是在还有这样一类场景,任务执行是有顺序控制的,例如常见的报表数据生成:

  • 启动数据分析任务,生成报表数据;
  • 报表数据存入指定位置数据容器;
  • 通知数据搬运任务,把数据写入报表库;

该场景在相对复杂的系统中非常常见,如果基于多线程来描述该过程,则需要线程之间通信协作,才能有条不紊的处理该场景业务。

2、等待通知机制

如上的业务场景,如果线程A生成数据过程中,线程B一直在访问数据容器,判断该过程的数据是否已经生成,则会造成资源浪费。正常的流程应该如图,线程A和线程B同时启动,线程A开始处理数据生成任务,线程B尝试获取容器数据,数据还没过来,线程B则进入等待状态,当线程A的任务处理完成,则通知线程B去容器中获取数据,这样基于线程等待和通知的机制来协作完成任务。

3、基础方法

等待/通知机制的相关方法是Java中Object层级的基础方法,任何对象都有该方法:

  • notify:随机通知一个在该对象上等待的线程,使其结束wait状态返回;
  • notifyAll:唤醒在该对象上所有等待的线程,进入对象锁争抢队列中;
  • wait:线程进入waiting等待状态,不会争抢锁对象,也可以设置等待时间;

线程的等待通知机制,就是基于这几个基础方法。

二、等待通知原理

1、基本原理

等待/通知机制,该模式下指线程A在不满足任务执行的情况下调用对象wait()方法进入等待状态,线程B修改了线程A的执行条件,并调用对象notify()或者notifyAll()方法,线程A收到通知后从wait状态返回,进而执行后续操作。两个线程通过基于对象提供的wait()/notify()/notifyAll()等方法完成等待和通知间交互,提高程序的可伸缩性。

2、实现案例

通过线程通信解决上述数据生成和存储任务的解耦流程。

public class NotifyThread01 {static Object lock = new Object() ;static volatile List<String> dataList = new ArrayList<>();public static void main(String[] args) throws Exception {Thread saveThread = new Thread(new SaveData(),"SaveData");saveThread.start();TimeUnit.SECONDS.sleep(3);Thread dataThread = new Thread(new AnalyData(),"AnalyData");dataThread.start();}// 等待数据生成,保存static class SaveData implements Runnable {@Overridepublic void run() {synchronized (lock){while (dataList.size()==0){try {System.out.println(Thread.currentThread().getName()+"等待...");lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("SaveData .."+ dataList.get(0)+dataList.get(1));}}}// 生成数据,通知保存static class AnalyData implements Runnable {@Overridepublic void run() {synchronized (lock){dataList.add("hello,");dataList.add("java");lock.notify();System.out.println("AnalyData End...");}}}
}

注意:除了dataList满足写条件,还要在AnalyData线程执行通知操作。

三、管道流通信

1、管道流简介

基本概念

管道流主要用于在不同线程间直接传送数据,一个线程发送数据到输出管道,另一个线程从输入管道中读取数据,进而实现不同线程间的通信。

实现分类

管道字节流:PipedInputStream和PipedOutputStream;

管道字符流:PipedWriter和PipedReader;

新IO管道流:Pipe.SinkChannel和Pipe.SourceChannel;

2、使用案例

public class NotifyThread02 {public static void main(String[] args) throws Exception {PipedInputStream pis = new PipedInputStream();PipedOutputStream pos = new PipedOutputStream();// 链接输入流和输出流pos.connect(pis);// 写数据线程new Thread(new Runnable() {public void run() {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));// 将从键盘读取的数据写入管道流PrintStream ps = new PrintStream(pos);while (true) {try {System.out.print(Thread.currentThread().getName());ps.println(br.readLine());Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();}}}}, "输入数据线程:").start();// 读数据线程new Thread(new Runnable() {public void run() {BufferedReader br = new BufferedReader(new InputStreamReader(pis));while (true) {try {System.out.println(Thread.currentThread().getName() + br.readLine());} catch (IOException e) {e.printStackTrace();}}}}, "输出数据线程:").start();}
}

写线程向管道流写入数据,读线程读取数据,完成基本通信流程。

四、生产消费模式

1、业务场景

基于线程等待通知机制:实现工厂生产一件商品,通知商店卖出一件商品的业务流程。

2、代码实现

public class NotifyThread03 {public static void main(String[] args) {Product product = new Product();ProductFactory productFactory = new ProductFactory(product);ProductShop productShop = new ProductShop(product);productFactory.start();productShop.start();}
}
// 产品
class Product {public String name ;public double price ;// 产品是否生产完毕,默认没有boolean flag ;
}
// 产品工厂:生产
class ProductFactory extends Thread {Product product ;public ProductFactory (Product product){this.product = product;}@Overridepublic void run() {int i = 0 ;while (i < 20) {synchronized (product) {if (!product.flag){if (i%2 == 0){product.name = "鼠标";product.price = 79.99;} else {product.name = "键盘";product.price = 89.99;}System.out.println("产品:"+product.name+"【价格:"+product.price+"】出厂...");product.flag = true ;i++;// 通知消费者product.notifyAll();} else {try {// 进入等待状态product.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
// 产品商店:销售
class ProductShop extends Thread {Product product ;public ProductShop (Product product){this.product = product ;}@Overridepublic void run() {while (true) {synchronized (product) {if (product.flag == true ){System.out.println("产品:"+product.name+"【价格"+(product.price*2)+"】卖出...");product.flag = false ;product.notifyAll(); //唤醒生产者} else {try {product.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}

流程描述:ProductFactory生成一件商品,通知商店售卖,通过flag标识判断控制是否进入等待状态,商店卖出商品后,再次通知工厂生产商品。

五、源代码地址

GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent

序号 文章标题
01 Java并发:线程的创建方式,状态周期管理
02 Java并发:线程核心机制,基础概念扩展
03 Java并发:多线程并发访问,同步控制

Java并发编程(04):线程间通信,等待/通知机制相关推荐

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

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

  2. java面试-Java并发编程(六)——线程间的通信

    多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同. 1. volatile.synchronized关键字 PS:关于volatile的详细介绍请移步至:Java ...

  3. 【Java 并发编程】线程简介 ( 进程与线程 | 并发概念 | 线程间通信 | Java 并发 3 特性 )

    文章目录 一.进程与线程 二.并发 三.线程间通信 四.Java 并发 3 特性 一.进程与线程 最开始是没有线程这个概念的 , 一个应用程序就是一个进程 , 应用程序运行时 , 如果还要处理与用户交 ...

  4. 【Java 并发编程】线程池机制 ( ThreadPoolExecutor 线程池构造参数分析 | 核心线程数 | 最大线程数 | 非核心线程存活时间 | 任务阻塞队列 )

    文章目录 前言 一.ThreadPoolExecutor 构造参数 二.newCachedThreadPool 参数分析 三.newFixedThreadPool 参数分析 四.newSingleTh ...

  5. 【Java 并发编程】线程池机制 ( 线程池示例 | newCachedThreadPool | newFixedThreadPool | newSingleThreadExecutor )

    文章目录 前言 一.线程池示例 二.newCachedThreadPool 线程池示例 三.newFixedThreadPool 线程池示例 三.newSingleThreadExecutor 线程池 ...

  6. 并发编程-04线程安全性之原子性Atomic包的4种类型详解

    文章目录 线程安全性文章索引 脑图 概述 原子更新基本类型 Demo AtomicBoolean 场景举例 原子更新数组 Demo 原子更新引用类型 Demo 原子更新字段类型 使用注意事项: Dem ...

  7. java并发编程与线程安全

    2019独角兽企业重金招聘Python工程师标准>>> 什么是线程安全 如果对象的状态变量(对象的实例域.静态域)具有可变性,那么当该对象被多个线程共享时就的考虑线程安全性的问题,否 ...

  8. JAVA并发编程3_线程同步之synchronized关键字

    在上一篇博客里讲解了JAVA的线程的内存模型,见:JAVA并发编程2_线程安全&内存模型,接着上一篇提到的问题解决多线程共享资源的情况下的线程安全问题. 不安全线程分析 public clas ...

  9. [转]Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  10. Java并发编程:线程的同步

    <?xml version="1.0" encoding="utf-8"?> Java并发编程:线程的同步 Java并发编程:线程的同步 Table ...

最新文章

  1. Error:Cannot set the value of read-only property 'outputFile' for ApkVariantOutputImpl_Decorated{apk
  2. apt-get could not get lock /var/lib/dpkg/lock报错
  3. BAP 使用DYNP_VALUES_READ来获取屏幕字段值
  4. SQL COUNT() 函数
  5. P5488-差分与前缀和【NTT,生成函数】
  6. 【渝粤教育】国家开放大学2018年秋季 0314-21T兽医基础 参考试题
  7. [转载] python语言中表示赋值的符号是_Python 语言中 asterisk 符号用法小结
  8. 计算器计算经纬距离_造价人如何熟练运用计算器来做题?
  9. 编译OpenJDK12:atomic.hpp: fatal error C1189: size_t is not WORD_SIZE(换了VS2017编译)
  10. three 实现绕物体旋转,卫星绕星球旋转
  11. 润乾报表性能优化问题
  12. npm install 报错 this command with --force, or --legacy-peer-deps
  13. 类和对象1:基础学习
  14. mysql32位的能装在64位的电脑上吗_32位电脑能装64位系统吗|怎么看32位电脑可不可以装64位系统-系统城...
  15. Segment Anything万物皆可分割
  16. 【JVM · 字节码】指令集 解析说明
  17. 【实战】物联网安防监控项目【5】———把模拟数据传输到web网页、web显示mjpeg-streamer视频图像
  18. php时间函数不准确,php date函数时间不对如何解决
  19. javascript基础复习之函数,定时器,erval函数
  20. Virtualbox源码分析22 NEM(Hyper-V兼容)3 Emulation Thread

热门文章

  1. JAVA中extends 与implements区别
  2. python 如何定义n个变量 (变量声明自动化)
  3. 6-4-1:STL之list——list的快速入门、常用接口
  4. 从零开始学PowerShell(1)初见基础命令
  5. 解决centos下缺少sasl.h的问题(#include <sasl/sasl.h>)
  6. Java 数组常用操作一(排序、元素位置查找、添加元素、获取长度、数组反向、最大值最小值、合并、范围填充)
  7. python制作各种条形图
  8. form 表单字段 autocomplete 设置为off 后仍然无效的解决方法
  9. reStructuredText学习
  10. appconfig文件中的配置节