Java线程间通信方式

news/2024/4/26 13:09:41/文章来源:https://blog.csdn.net/u010132993/article/details/130353846

前文了解了线程的创建方式和状态切换,在实际开发时,一个进程中往往有很多个线程,大多数线程之间往往不是绝对独立的,比如说我们需要将A和B 两个线程的执行结果收集在一起然后显示在界面上,又或者比较典型的消费者-生产者模式,在这些场景下,线程间通信成了我们必须使用的手段,那么线程之间怎么通信呢?

线程间通信方式,从实现本质来讲,主要可以分为两大类共享内存和消息传递。

相信大家还记得,在内存模型一节,我们提到多线程并发情况下的三大特性,原子性,有序性,可见性,其所对应的解决方案就可以用来实现线程间通信,这些解决方案的本质就是共享内存。

对于消息传递而言,最经典的实现就是我们的Handler机制,在子线程使用主线程的Handler对象将一些信息发送到主线程以便进行处理。

下面我们来看一些线程间通信的典型实现

Object.wait/Object.notify

对于Object对象而言,其提供了等待/通知机制以便实现线程间通信,由于Object是Java中所有类的父类,也就意味着Java中所有对象都支持通知/等待机制,与该机制关联的主要有五个方法:

方法名称描述备注
wait()线程执行中调用对象的wait方法可以使得当前线程进入WAITING状态,只有等待其他线程的通知或被中断才会返回,需要注意的是,调用wait方法后,会释放对象的锁/
wait(long timeout)与wait含义一致,不同的是通过timeout指定了超时时间,如果时间到了还没收到通知就超时返回/
wait(long timeout, int nanos)超时管控更加精确,第二个参数单位为毫微秒/
notify通知一个在对象上等待的线程使其从wait对象返回/
notifyAll通知所有等待在该对象上的线程/

以Object.wait/Object.notify实现一个典型的消息者生产者模型,消费者对变量做-1操作,生产者对变量做+1操作,代码如下:

// 盘子
public class Number {// 盘子当前容量,是否有内容private int mCount = 0;//对盘子容量进行+1操作public void inc() {if (mCount != 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}mCount++;System.out.println(Thread.currentThread().getName()+",mCount+1,mCount="+mCount);this.notifyAll();}//对盘子容量进行-1操作public void dec() {if (mCount == 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}mCount--;System.out.println(Thread.currentThread().getName()+",mCount-1,mCount="+mCount);this.notifyAll();}
}
    public static void main(String[] args) {Number number = new Number();// 生产者线程Thread incThread = new Thread(new Runnable() {@Overridepublic void run() {number.inc();}});incThread.setName("Inc Thread");incThread.start();// 消费者线程Thread decThread = new Thread(new Runnable() {@Overridepublic void run() {number.dec();}});decThread.setName("Dec Thread");decThread.start();}

如上述代码备注,其中Inc Thread为生产者线程,当盘子内容为0时,每次向盘子Number中放一个内容,消费者线程Dec Thread当盘子有内容时,消耗内容,让盘子内容变为0.运行输出如下:

1-4-3-1

糟糕,正确运行一个循环后,抛出了IllegalMonitorStateException,为什么会这样呢?这个异常是什么意思?

遇事不决看源码,IllegalMonitorStateException的类说明如下:

Thrown to indicate that a thread has attempted to wait on an object’s monitor or to notify other threads waiting on an object’s monitor without owning the specified monitor.

翻译过来的意思就是当线程在没有持有特定的锁的情况下试图等待对象锁或者通知其他线程等待对象锁会抛出此异常,有点拗口,先放置,即然我们调用了wait/notifyAll这两个方法,不妨看下这两个方法的说明,看是否有新的提示,wait方法说明如下:

/*** Causes the current thread to wait until another thread invokes the* {@link java.lang.Object#notify()} method or the* {@link java.lang.Object#notifyAll()} method for this object.* In other words, this method behaves exactly as if it simply* performs the call {@code wait(0)}.* <p>* The current thread must own this object's monitor. The thread* releases ownership of this monitor and waits until another thread* notifies threads waiting on this object's monitor to wake up* either through a call to the {@code notify} method or the* {@code notifyAll} method. The thread then waits until it can* re-obtain ownership of the monitor and resumes execution.* <p>* As in the one argument version, interrupts and spurious wakeups are* possible, and this method should always be used in a loop:* <pre>*     synchronized (obj) {*         while (&lt;condition does not hold&gt;)*             obj.wait();*         ... // Perform action appropriate to condition*     }* </pre>* This method should only be called by a thread that is the owner* of this object's monitor. See the {@code notify} method for a* description of the ways in which a thread can become the owner of* a monitor.** @throws  IllegalMonitorStateException  if the current thread is not*               the owner of the object's monitor.* @throws  InterruptedException if any thread interrupted the*             current thread before or while the current thread*             was waiting for a notification.  The <i>interrupted*             status</i> of the current thread is cleared when*             this exception is thrown.* @see        java.lang.Object#notify()* @see        java.lang.Object#notifyAll()*/
public final void wait() throws InterruptedException {wait(0);
}

在上述说明中反复提到 The current thread must own this object’s monitor. This method should only be called by a thread that is the owner of this object’s monitor.也就是说在调用Object.wait方法前,当前线程必须持有该对象的锁,获取锁的方法很简单,wait方法说明中也有,通过synchronized关键词,那么正确的调用代码如下所示:

public class Number {private int mCount = 0;public void inc() {synchronized (this) {if (mCount != 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}mCount++;System.out.println(Thread.currentThread().getName()+",mCount+1,mCount="+mCount);this.notifyAll();}}public void dec() {synchronized (this) {if (mCount == 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}mCount--;System.out.println(Thread.currentThread().getName()+",mCount-1,mCount="+mCount);this.notifyAll();}}
}

重新运行代码,输出如下:

1-4-3-2

这里可以看出,只运行了一个循环,那么怎么让它一直运行呢?将if修改称while即可,以生产10次为例,如需一直生产消息,使用while(true)即可,代码及输出如下:

public class Number {private int mCount = 0;private int mIncTimes = 0;private int mDecTimes = 0;public void inc() {synchronized (this) {while (mIncTimes < 10) {if (mCount != 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}mCount++;mIncTimes ++;System.out.println(Thread.currentThread().getName()+",mCount+1,mCount="+mCount+",mIncTimes:"+mIncTimes);this.notifyAll();}}}public void dec() {synchronized (this) {while (mDecTimes < 10) {if (mCount == 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}mCount--;mDecTimes++;System.out.println(Thread.currentThread().getName()+",mCount-1,mCount="+mCount+",mDecTimes:"+mDecTimes);this.notifyAll();}}}
}

1-4-3-3

综上,使用Object.wait/Object.notify/Object.notifyAll时,切记其必须先使用关键词获取同个Object的对象锁,否则就会抛出IllegalMonitorStateException异常

Semaphore

Semaphore翻译为信号量,一个信号量维护一组许可,在调用acquire方法时阻塞,直到获取许可,在调用release的时候释放占用,从而唤醒阻塞的某个线程。信号量操作类似于停车场车辆管理,初始时停车场有5个车位,当停车场内部5个车位全占满时,此时可用资源为0,即信号量可用许可数量为0,其他车辆想停车就只能在停车场外排队阻塞(相当于调用acquire),当一辆车辆从停车场驶出时(相当于调用release方法),此时信号量许可数量为1,唤醒一个等待停车的车辆进入停车辆,自身可用许可数量再次为0,依此往复。

对于只有一个许可的信号量而言,其可用许可数量为0或1,故被称为二进制信号量,对于有多个正整数可用许可数据的信号量而言,其被称为通用信号量。需要注意在执行acquire时信号量本身并不会持有同步锁,因为这样会影响被释放的许可进入可用许可池中。

二进制信号量,不同于其他锁机制,要求释放锁的线程和获取锁的线程是同一个,也就意味着我们可以在其他线程释放二进制信号量以完成死锁恢复。

下面我们以二进制信号量实现消费者生产者模式,代码如下(生产消费4次即停止):

public class Counter {private int mCount = 0;public void incCount() {mCount ++;}public void decCount() {mCount--;}public int getCount() {return mCount;}
}// Main主类代码
private static int mIncTimes = 0;
public static void main(String[] args) {Counter counter = new Counter();Semaphore semaphore = new Semaphore(1);Thread incThread = new Thread(new Runnable() {@Overridepublic void run() {while (mIncTimes < 4) {try {semaphore.acquire();if (counter.getCount() == 0) {counter.incCount();mIncTimes ++;System.out.println("Inc Thread ++,current count is:" + counter.getCount());}} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release();}}}});incThread.setName("Inc Thread");incThread.start();Thread decThread = new Thread(new Runnable() {@Overridepublic void run() {while (mIncTimes < 4) {try {semaphore.acquire();if (counter.getCount() != 0) {counter.decCount();System.out.println("Dec Thread --,current count is:" + counter.getCount());}} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release();}}}});decThread.setName("Dec Thread");decThread.start();
}

运行结果如下:

1-4-3-6

内存一致性影响,要求一个线程中的release操作和另一个线程中的acquire操作必须存在happen-before关系

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_103002.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【论文写作】如何写引言?应该思考什么问题?总体架构!!!

结构 大多数的科技论文都聚焦于简单地说明&#xff0c;做了什么&#xff0c;发现了什么&#xff1f;虽然这个可以帮助你写出一篇研究型论文当中的核心的东西&#xff08;方法论和结果&#xff09;&#xff0c;但是不能完全把引言的部分完成。在这篇文章当中&#xff0c;将展示…

4 redis高可用

所谓的高可用&#xff0c;也叫HA&#xff08;High Availability&#xff09;&#xff0c;是分布式系统架构设计中必须考虑的因素之一&#xff0c;它通常是指&#xff0c;通过设计减少系统不能提供服务的时间。如果在实际生产中&#xff0c;如果redis只部署一个节点&#xff0c;…

从FMCW毫米波雷达系统的性能参数理解4D成像毫米波雷达的设计思路

本文编辑&#xff1a;调皮哥的小助理 站在设计雷达的角度看&#xff0c;其实无论是传统的3D毫米波雷达&#xff0c;还是如今的4D毫米波成像雷达&#xff0c;其雷达系统性能参数都遵循一个原则&#xff0c;即&#xff1a; d res ⋅ v res ⋅ θ res d max ⁡ ⋅ v max ⁡ ⋅ …

ESP8266通过MQTT协议连接onenet云平台

中国移动onenet平台 文章目录 中国移动onenet平台前言一、onenet平台二、ESP82661.完整代码2.联网代码3.连云代码4.数据处理 总结 前言 最近在弄onenet平台&#xff0c;用arduino结合esp8266&#xff0c;就是不知道怎么回事&#xff0c;一直连不上wifi&#xff0c;然后就用esp…

Linux下使用Mysql 第一天

目录 安装mysql 更改账户名和密码 启动/关闭mysql mysql的基本操作 数据库CURD 创建数据库 查看数据库 修改数据库 删除数据库 表的CURD 创建表 查看表 修改表 删除表 表数据的CURD create数据 Retrieve数据 update数据 delete数据 DML和DDL的区别&#xf…

高分辨率光学遥感图像水体分类综述2022.03

本文是Water body classification from high-resolution optical remote sensing imagery: Achievements and perspectives的学习笔记。 相关资源被作者整理到&#xff1a;这里 文章目录 Introduction基本知识 挑战和机遇挑战1. 有限的光谱信息和小场景覆盖2. 形状、大小和分布…

【JAVA-模块五 数组】

JAVA-模块五 数组 一、数组&#xff08;一维&#xff09;1.1数组是什么&#xff1f;1.2java中数组静态初始化&#xff1a;&#xff08;存&#xff09;两种定义格式&#xff1a;数组初始化格式&#xff1a;静态初始化后&#xff0c;打印数组名&#xff1a; 1.3 数组元素访问&…

javaweb学生在线考试系统dzkf10程序

打分&#xff09;、系统管理&#xff08;数据备份&#xff09;等功能操作。 以学生的身份在登录页面输入账号和密码&#xff0c;经过数据库身份验证&#xff0c;验证成功后登录系统主页&#xff0c;可以使用个人资料管理、试卷查看、在线考试、在线答疑、个人考试成绩查询等功能…

Oracle的学习心得和知识总结(二十三)|Oracle数据库Real Application Testing之Database Replay相关视图

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《Oracle Database SQL Language Reference》 2、参考书籍&#xff1a;《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

LVS负载均衡-DR

1.DR模式中每台主机都有一个VIP地址 虚拟网址放在lo网卡上&#xff08;回环网卡&#xff09; arp_ignore1 Arp_announce2 系统不使用IP包的源地址来设置ARP请求的源地址&#xff0c;而选择发送接口的IP地址 2.内核参数修改 3.vim /etc/rc.conf 开机自启动 Chmod x /etc/rc.d…

【翻译一下官方文档】之uniapp的导航条设置

目录 uni.setNavigationBarTitle(OBJECT) uni.setNavigationBarColor(OBJECT) uni.hideHomeButton(OBJECT) uni.setNavigationBarTitle(OBJECT) 动态设置当前页面的标题。 OBJECT参数说明 参数类型必填说明titleString是页面标题successFunction否接口调用成功的回调函数fai…

卷积神经网络总结

1、卷积核 进行互相关运算。 卷积核的大小一般是奇数。 卷积核的本质类似于提取局部特征&#xff08;过滤器&#xff09;&#xff0c;当层层卷积核叠加后&#xff0c;卷积核的感受野变大&#xff0c;卷积核的作用逐渐向提取全局抽象特征靠近。最后一层的神经元应该对整个输入…

SpringBoot中@EnableAsync和@Async注解的使用

目录 1.EnableAsync 注解1.1 配置类使用示例1.2 复制请求上下文 2.用法1&#xff1a;Async 注解2.1 测试Controller2.2 测试Service2.3 测试ServiceImpl2.4.测试 4.用法2&#xff1a;直接使用 taskExecutor 做异步4.1 重新实现&#xff1a;测试ServiceImpl4.2 测试 5.Async异步…

ArcGIS三体阴影(影像三维)显示马赛克?

我们经常基于ArcGIS通过DEM来做山体阴影 但是有时候你一放大就会出现很强的马赛克的效果 还有我们在利用ArcScene建三维场景 即使数据分辨率很高也会出现马赛克效果 那怎么来解决这个问题呢 让我们的山体阴影显示更加细腻 三维没有马赛克的效果呢&#xff1f; 右键图层选择如…

地铁站人流检测硬件部分

目录 一、概述 二、驱动程序 2.1debug串口 2.2体重传感器HX711 2.3滴答定时器 2.4ESP8266 2.5人体检测 2.6 IIC的GPIO 2.7 OLED的IIC 2.8 LED 三、应用 四、中断 一、概述 使用STM32C8T6作为主控 A9 ---> tx&#xff08;调试串口&#xff09; A10 ---> …

android framework-ActivityManagerService(AMS)下

一、ActivityThread \frameworks\base\core\java\android\app\ActivityThread.java 1.1、main public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// Install selective syscall interceptionAnd…

Hudi数据湖技术之核心概念

目录 1 基本概念1.1 时间轴Timeline1.2 文件管理1.3 索引Index 2 存储类型2.1 计算模型2.1.1 批式模型&#xff08;Batch&#xff09;2.1.2 流式模型&#xff08;Stream&#xff09;2.1.3 增量模型&#xff08;Incremental&#xff09; 2.2 查询类型&#xff08;Query Type&…

4.3调整基类成员在派生类中的访问属性的方法

同名成员 在定义派生类的时候&#xff0c;C语言允许派生类与基类中的函数名相同。如果在派生类中定义了与基类中相同的成员&#xff0c;则称派生类成员覆盖了基类的同名成员&#xff0c;在派生类中使用这个名字意味着访问在派生类中重新说明的成员。为了在派生类中使用基类的同…

C++ -4- 类和对象(下)

文章目录 1.初始化列表什么是初始化列表&#xff1f;初始化列表的 意义及使用 2.explicit关键字单参数构造函数&#xff08;C98&#xff09;多参数的构造函数&#xff08;C11&#xff09;&#xff08;了解&#xff09; 3.static静态成员静态成员变量与静态成员函数静态成员变量…

前端02:CSS选择器等基础知识

CSS基础选择器、设置字体样式、文本样式、CSS的三种引入方式、能使用Chrome调试工具调试样式 HTML专注做结构呈现&#xff0c;样式交给CSS&#xff0c;即结构&#xff08;HTML&#xff09;和样式CSS相分离 CSS主要由量分布构成&#xff0c;选择器以及一条或多条声明 选择器&…