Java并发编程(04):线程间通信,等待/通知机制
本文源码: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 {
@Override
public 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 {
@Override
public 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;
}
@Override
public 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 ;
}
@Override
public 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):线程间通信,等待/通知机制的更多相关文章
- java并发编程实战《六》等待-通知机制
用"等待-通知"机制优化循环等待 前言 在破坏占用且等待条件的时候,如果转出账本和转入账本不满足同时在文件架上这个条件,就用死循环的方式来循环等待. 1 // 一次性申请转出账户和 ...
- Java 线程间通信 —— 等待 / 通知机制
本文部分摘自<Java 并发编程的艺术> volatile 和 synchronize 关键字 每个处于运行状态的线程,如果仅仅是孤立地运行,那么它产生的作用很小,如果多个线程能够相互配合 ...
- Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- 19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- 《java多线程编程核心技术》不使用等待通知机制 实现线程间通信的 疑问分析
不使用等待通知机制 实现线程间通信的 疑问分析 2018年04月03日 17:15:08 ayf 阅读数:33 编辑 <java多线程编程核心技术>一书第三章开头,有如下案例: ...
- Java多线程编程(6)--线程间通信(下)
因为本文的内容大部分是以生产者/消费者模式来进行讲解和举例的,所以在开始学习本文介绍的几种线程间的通信方式之前,我们先来熟悉一下生产者/消费者模式. 在实际的软件开发过程中,经常会碰到如下场景 ...
- 【转】Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...
- Java多线程编程核心技术---线程间通信(二)
通过管道进行线程间通信:字节流 Java提供了各种各样的输入/输出流Stream可以很方便地对数据进行操作,其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据,一个线程发送 ...
- Java多线程编程核心技术---线程间通信(一)
线程是操作系统中独立的个体,但这些个体如果不经过特殊处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一.线程间通信可以使系统之间的交互性更强大,在大大提高CPU利用率的同时还会使程序员对各 ...
随机推荐
- 【题解】P4570 [BJWC2011]元素 - 线性基 - 贪心
P4570 [BJWC2011]元素 声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。 题目描述 给你 \(n\) 个二元组 \( ...
- 移动(appium)自动化测试-爬虫的另一种手段
appium自动化测试环境搭建: 1.Python环境(推荐2.7)和jdk. 2.Adb工具的下载:自己单独下载adb.夜神模拟器自带和Android sdk 3.Apk安装介质:真机.Androi ...
- Zookeepe的安装和集群的配置
Zookeepe的安装和集群的配置 一.Zookeeper的简介 Zookeeper是个框架 二.Zookeeper在Linux上安装 官方提供的下载服务器 地址1:http://mirror.bit ...
- Winsock select server 与 client 示例代码
参考 https://www.winsocketdotnetworkprogramming.com/winsock2programming/winsock2advancediomethod5.html ...
- Google Play商店为预注册的游戏和应用提供自动安装功能
谷歌 Play 商店一直在准备一项功能,它可以自动安装用户预先注册的应用程序和游戏.似乎该功能现已开始向第一批用户推出.有些人在预注册时会看到一个新选项,使他们能够利用发布时自动安装的功能. 用户在 ...
- 计算3的n次幂htm代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Python语言类型
Python是一门动态解释型的强类型语言. 对这句话进行解析,语言分为动态的和静态的,编译型和解释型的,强类型的和弱类型的语言之分. 下面对三种不同维度的类型的语言进行解释: 1.编译型和解释型 差别 ...
- Ubuntu搭建NTP服务器
NTP简介 NTP是Network Time Protocol的缩写,又称为网络时间协议.是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)做同步化,它可以提 ...
- .NET Micro Framework 4.2 beta 源码探析
.NET Micro Framework 4.2 beta发布已经有一段时间了,一直没有腾出时间研究,昨天因为LWIP协议栈的原因(感觉上一个版本有点问题)刚 下了代码,所以抽空研究了一下. ...
- C++编程入门--No.7
输出特殊图案,请在c环境中运行,看一看,Very Beautiful! 1.程序分析:字符共有256个.不同字符,图形不一样. 2.程序源代码: #include "stdio.h" ...