线程间的通信方式

article/2025/9/24 18:33:00

对共享数据进行更改的时候,先到主内存中拷贝一份到本地内存中,然后进行数据的更改,再重新将数据刷到主内存,这中间的过程,其他线程是看不到的。

1、为什么需要线程通信

线程是操作系统调度的最小单位,有自己的栈空间,可以按照既定的代码逐步的执行,但是如果每个线程间都孤立的运行,那就会造资源浪费。所以在现实中,我们需要这些线程间可以按照指定的规则共同完成一件任务,所以这些线程之间就需要互相协调,这个过程被称为线程的通信。

线程的通信可以被定义为:

线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺。

2、线程通信的方式

线程通信主要可以分为三种方式,分别为共享内存消息传递管道流。每种方式有不同的方法来实现

  • 共享内存:线程之间共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信。
volatile共享内存
  • 消息传递:线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。
wait/notify等待通知方式
join方式
  • 管道流
管道输入/输出流的形式

2.1共享内存

在学习Volatile之前,我们先了解下Java的内存模型,

 

在java中,所有堆内存中的所有的数据(实例域、静态域和数组元素)存放在主内存中可以在线程之间共享,一些局部变量、方法中定义的参数存放在本地内存中不会在线程间共享。线程之间的共享变量存储在主内存中,本地内存存储了共享变量的副本。如果线程A要和线程B通信,则需要经过以下步骤

①线程A把本地内存A更新过的共享变量刷新到主内存中
②线程B到内存中去读取线程A之前已更新过的共享变量。

这保证了线程间的通信必须经过主内存。下面引出我们要学习的关键字volatile

volatile有一个关键的特性:保证内存可见性,即多个线程访问内存中的同一个被volatile关键字修饰的变量时,当某一个线程修改完该变量后,需要先将这个最新修改的值写回到主内存,从而保证下一个读取该变量的线程取得的就是主内存中该数据的最新值,这样就保证线程之间的透明性,便于线程通信。

代码实现

/*** @Author: Simon Lang* @Date: 2020/5/5 15:13*/
public class TestVolatile {private static volatile boolean flag=true;public static void main(String[] args){new Thread(new Runnable() {public void run() {while (true){if(flag){System.out.println("线程A");flag=false;}}}}).start();
​
​new Thread(new Runnable() {public void run() {while (true){if(!flag){System.out.println("线程B");flag=true;}}}}).start();}
}
​

测试结果:线程A和线程B交替执行

 

2.2消息传递

2.2.1wait/notify等待通知方式

从字面上理解,等待通知机制就是将处于等待状态的线程将由其它线程发出通知后重新获取CPU资源,继续执行之前没有执行完的任务。最典型的例子生产者--消费者模式

有一个产品队列,生产者想要在队列中添加产品,消费者需要从队列中取出产品,如果队列为空,消费者应该等待生产者添加产品后才进行消费,队列为满时,生产者需要等待消费者消费一部分产品后才能继续生产。队列可以认为是java模型里的临界资源,生产者和消费者认为是不同的线程,它们需要交替的占用临界资源来进行各自方法的执行,所以就需要线程间通信。

生产者--消费者模型主要为了方便复用和解耦,java语言实现线程之间的通信协作的方式是等待/通知机制

等待/通知机制提供了三个方法用于线程间的通信

wait()当前线程释放锁并进入等待(阻塞)状态notify()唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后继续竞争锁notifyAll()唤醒所有正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后继续竞争锁

等待/通知机制是指一个线程A调用了对象Object的wait()方法进入等待状态,而另一线程B调用了对象Object的notify()或者notifyAll()方法,当线程A收到通知后就可以从对象Object的wait()方法返回,进而执行后序的操作。线程间的通信需要对象Object来完成,对象中的wait()、notify()、notifyAll()方法就如同开关信号,用来完成等待方和通知方的交互。

测试代码

 public class WaitNotify {static boolean flag=true;static Object lock=new Object();
​public static void main(String[] args) throws InterruptedException {Thread waitThread=new Thread(new WaitThread(),"WaitThread");waitThread.start();TimeUnit.SECONDS.sleep(1);Thread notifyThread=new Thread(new NotifyThread(),"NotifyThread");notifyThread.start();}//等待线程static class WaitThread implements Runnable{public void run() {//加锁synchronized (lock){//条件不满足时,继续等待,同时释放lock锁while (flag){System.out.println("flag为true,不满足条件,继续等待");try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}//条件满足System.out.println("flag为false,我要从wait状态返回继续执行了");
​}
​}}//通知线程static class NotifyThread implements Runnable{
​public void run() {//加锁synchronized (lock){//获取lock锁,然后进行通知,但不会立即释放lock锁,需要该线程执行完毕lock.notifyAll();System.out.println("设置flag为false,我发出通知了,但是我不会立马释放锁");flag=false;}}}}

测试结果

NOTE:使用wait()、notify()和notifyAll()需要注意以下细节

  • 使用wait()、notify()和notifyAll()需要先调用对象加锁
  • 调用wait()方法后,线程状态由Running变成Waiting,并将当前线程放置到对象的等待队列
  • notify()和notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()和notifyAll()的线程释放锁之后等待线程才有机会从wait()返回
  • notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部转移到同步队列,被移到的线程状态由Waiting变为Blocked。
  • 从wait()方法返回的前提是获得调用对象的锁

 

其实等待通知机制有有一个经典的范式,该范式可以分为两部分,分别是等待方(消费者)和通知方(生产者)

  • 等待方
synchronized(对象){
while(条件不满足){
对象.wait()
}
对应的处理逻辑
}
  • 通知方
synchronized(对象){
改变条件
对象.notifyAll
}

2.2.2join方式

在很多应用场景中存在这样一种情况,主线程创建并启动子线程后,如果子线程要进行很耗时的计算,那么主线程将比子线程先结束,但是主线程需要子线程的计算的结果来进行自己下一步的计算,这时主线程就需要等待子线程,java中提供可join()方法解决这个问题。

join()方法的作用是:在当前线程A调用线程B的join()方法后,会让当前线程A阻塞,直到线程B的逻辑执行完成,A线程才会解除阻塞,然后继续执行自己的业务逻辑,这样做可以节省计算机中资源。

测试代码

public class TestJoin {public static void main(String[] args){Thread thread=new Thread(new Runnable() {@Overridepublic void run() {System.out.println("线程0开始执行了");}});thread.start();for (int i=0;i<10;i++){JoinThread jt=new JoinThread(thread,i);jt.start();thread=jt;}
​}
​static class JoinThread extends Thread{private Thread thread;private int i;
​public JoinThread(Thread thread,int i){this.thread=thread;this.i=i;}
​@Overridepublic void run() {try {thread.join();System.out.println("线程"+(i+1)+"执行了");} catch (InterruptedException e) {e.printStackTrace();}}}
}
​

测试结果

 

NOTE:每个线程的终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从join方法返回,实际上,这里涉及了等待/通知机制,即下一个线程的执行需要接受前驱线程结束的通知。

2.3管道输入/输出流

管道流是是一种使用比较少的线程间通信方式,管道输入/输出流和普通文件输入/输出流或者网络输出/输出流不同之处在于,它主要用于线程之间的数据传输,传输的媒介为管道。

管道输入/输出流主要包括4种具体的实现:PipedOutputStrean、PipedInputStrean、PipedReader和PipedWriter,前两种面向字节,后两种面向字符。

java的管道的输入和输出实际上使用的是一个循环缓冲数组来实现的,默认为1024,输入流从这个数组中读取数据,输出流从这个数组中写入数据,当这个缓冲数组已满的时候,输出流所在的线程就会被阻塞,当向这个缓冲数组为空时,输入流所在的线程就会被阻塞。

 

buffer:缓冲数组,默认为1024
out:从缓冲数组中读数据
in:从缓冲数组中写数据

测试代码

public class TestPip {public static void main(String[] args) throws IOException {PipedWriter writer  = new PipedWriter();PipedReader reader = new PipedReader();//使用connect方法将输入流和输出流连接起来writer.connect(reader);Thread printThread = new Thread(new Print(reader) , "PrintThread");//启动线程printThreadprintThread.start();int receive = 0;try{//读取输入的内容while((receive = System.in.read()) != -1){writer.write(receive);}}finally {writer.close();}}
​private static class Print implements Runnable {private PipedReader reader;
​public Print(PipedReader reader) {this.reader = reader;}
​@Overridepublic void run() {int receive = 0;try{while ((receive = reader.read()) != -1){//字符转换System.out.print((char) receive);}}catch (IOException e) {System.out.print(e);}}}
}
​

测试结果

 

NOTE:对于Piped类型的流,必须先进性绑定,也就是调用connect()方法,如果没有将输入/输出流绑定起来,对于该流的访问将抛出异常。


http://chatgpt.dhexx.cn/article/6mhJxvyl.shtml

相关文章

进程和线程的几种通信方式

进程之间通信的几种方式 1. 管道&#xff1a;是内核里面的一串缓存 管道传输的数据是单向的&#xff0c;若相互进行通信的话&#xff0c;需要进行创建两个管道才行的。 2. 消息队列&#xff1a; 例如&#xff0c;A进程给B进程发送消息&#xff0c;A进程把数据放在对应的消息队…

线程的几种通信方式

目录 一、Object的wait()、notify()、notifyAll()方法 二、Condition的await()、signal()、signalAll()方法 三、CountDownLatch 四、CyclicBarrier 五、Semaphore 线程间的通信方式常用的有如下几种&#xff1a; Object的wait()、notify()、notifyAll()方法&#xff1b; …

线程间的通信方法

线程间的通信方法 1. 线程通信简介 一般而言&#xff0c;在一个应用程序&#xff08;即进程&#xff09;中&#xff0c;一个线程往往不是孤立存在的&#xff0c;常常需要和其它线程通信&#xff0c;以执行特定的任务。如主线程和次线程&#xff0c;次线程与次线程&#xff0c…

Matlab基本操作函数 abs函数

分享一下我老师大神的人工智能教程&#xff01;零基础&#xff0c;通俗易懂&#xff01;http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章。分享知识&#xff0c;造福人民&#xff0c;实现我们中华民族伟大复兴&#xff01; 1、abs函数&#xff1a;数值的绝对值和复数…

MATLAB中FFT的整理

作为一个资深的健忘症患者&#xff0c;需要把每次用都忘记的FFT问题进行整理。 FFT可将信号从时域转换到频域。 首先是一些简单常识&#xff1a; 采样周期&#xff1a;两次采样之间的时间间隔。 采样频率&#xff1a;1/采样周期。每秒采样的点数。&#xff08;注意&#xff1a…

matlab中abs函数,matlababs是什么意思 是是是什么意思

matlababs是什么意思 是是是什么意思以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! matlab 中的abs函数什么意思 编程知识 matlab中的abs(x)是去绝对值的函数 例如:x=-1.23 abs(x) ans 1.23 以上即是取了-1.23…

Matlab 用法

MATLAB基础&#xff1a; 清除命令 clc 清空命令行的命令 clf 清除当前figure中的内容 clear 清除工作区变量 close all 关闭所有图形窗口 清除命令通常放在代码最前方&#xff0c;避免其他变量或代码的干扰 变量命名规则 ①以英文字母开头&#xff0c;可包含英文字母、…

abs 三种功能及代码详解 matlab函数

1.abs函数功能 求实数的绝对值、复数的模、字符串的ASCII值 2.基本用法 abs(x)函数是对数组元素进行绝对值处理的函数。 函数的定义域包括复数。 对于复数xab*i&#xff0c;有abs(x)sqrt(a2b2)。 3.代码 clc; clear all;a -7; b 12i; abs(a…

android 屏幕坐标总结

android 屏幕坐标好多个&#xff0c;有时候傻傻分不清楚&#xff0c;经常记错&#xff0c;然后只能一个个试。尴尬&#xff5e;&#xff5e; 把它们总结下来&#xff0c;以备不时之需嘿嘿。 一、视图坐标 最外面一层是屏幕&#xff0c;左上角是坐标原点&#xff0c;向右向…

【Unity3D】世界坐标与屏幕坐标

Unity3D由于是在三维世界中编程&#xff0c;而最终的结果是需要反馈到肉眼所示的2D屏幕之上的。这就产生了一种比较需要考虑的问题&#xff0c;尤其在一些涉及屏幕与Unity3D的3D世界交互的情况。网络上对于这方面的文字&#xff0c;大部分罗列了许许多多文字与代码或者API&…

Unity世界坐标转换屏幕坐标(测试)

下面展示一下上一篇说的两种实现方式打包文件在不同分辨率下的效果 1.WorldToScreenPoint 1920 * 1080 800 * 600 2.WorldToViewportPoint 1920 * 1080 800 * 600 总结 可以看到四种情况全部都显示正确&#xff0c;我们再看一下原来的代码 public Vector3 GetScreenPositio…

Unity 屏幕坐标转UI坐标

1&#xff1a;屏幕坐标转UI坐标 首先我们来明确下三个坐标概念&#xff1a; 世界坐标&#xff1a;指的是Transform组件的position字段 UI坐标&#xff1a;指的是RectTransform组件的anchoredPosition字段 屏幕坐标&#xff1a;指的是屏幕空间的坐标 (也可以说是相机空间的坐…

经纬度转换成屏幕坐标

学期projet总结&#xff1a; 当把点的数据和线的数据读进来之后&#xff0c;为了画出地图还有最重要的一步就是把实际的经纬度转换成屏幕像素点的坐标。在找老师讨论之前&#xff0c;我在网上查资料&#xff0c;找到了下边链接的文章&#xff0c;并按照这个方法画出了地图。 …

Unity-世界坐标与屏幕坐标

transform.position.x和transform.position.y的值含义是世界坐标。 世界坐标与屏幕坐标有时一样&#xff0c;有时不同&#xff0c;这和Canvas的渲染模式有关。 Canvas共有三种渲染模式 Screen Space - Overlay (此模式UGUI层一直在最上面&#xff0c;其他例如粒子等物体一直…

Unity 世界坐标、屏幕坐标、UGUI 坐标 相互转换

Unity 世界坐标、屏幕坐标、UGUI 坐标 相互转换 坐标转换是游戏开发过程中必不可少的环节 看下图 世界坐标、屏幕坐标、UI 坐标 三种坐标系的转换过程&#xff0c;此文章中的 UI 坐标特指 UGUI 坐标 从上图可以看到&#xff0c;世界坐标 和 UI 坐标 需要通过 屏幕坐标作为中间…

Android得到控件在屏幕中的坐标

getLocationOnScreen ,计算该视图在全局坐标系中的x,y值,(注意这个值是要从屏幕顶端算起,也就是索包括了通知栏的高度)//获取在当前屏幕内的绝对坐标 getLocationInWindow ,计算该视图在它所在的widnow的坐标x,y值,//获取在整个窗口内的绝对坐标 (不是很理解= =、) …

安卓 获取屏幕坐标(点击屏幕获取坐标)

工具下载&#xff1a; 实现原理&#xff1a;创建一个背景透明的Activity, 点击屏幕时获取坐标信息并显示。在悬浮窗中调用该Activity&#xff0c;可以获取所有界面的坐标信息。 package sc.tool.screen;import sc.tool.component.ActivityComponent; import android.content.Co…

Unity世界坐标转换屏幕坐标(详解)

我们先通过简单的操作实现一下基础的UI跟随物体移动的功能&#xff0c;首先我们在场景中建立一个Canvas并且添加一个图片作为按钮&#xff0c;之后我们添加一个3d物体作为跟随目标&#xff0c;效果如下图所示 我们配置一下UICanvas的属性&#xff0c;书写对应的自定义类并添加至…

地理坐标(经纬度坐标)和屏幕坐标(xy坐标)间的转换

在我们的屏幕上&#xff0c;有一张地图&#xff0c;这张地图经过缩放、平移、旋转&#xff0c;最终地理坐标和屏幕坐标的关系大致如下图所示&#xff1a; 这种关系要怎么描述呢&#xff1f;我们可以假设地图是一张纸&#xff0c;而屏幕是一堵墙。只要我们有两个图钉&#xff0c…

Windows的三种坐标系:屏幕坐标系,非客户区坐标系,客户区坐标系

1. 屏幕坐标系&#xff1a;以屏幕的左上角为原点&#xff0c;如图所示GetWindowRect() 函数获得的 RECT 就是以屏幕坐标系算的。 2. 非客户区坐标系(窗口坐标系)包括标题栏的部分。GetWindowDC 返回的设备环境就是基于此坐标系&#xff0c;一般只在 WM_NCPAINT 消息中使用。 3.…