线程间通信的常用方式

1.简介

线程通信简单来说就是实现线程的交替工作,传递信息。例如在一个方法中我有两个线程A和B在运行,我希望线程A先向一个集合里面循环新增数据,当增加到第五次的时候,线程B才开始执行其他的操作。
线程间通信的模型有两种:共享内存消息传递

2.共享内存模型

1)volatile关键字

关于volatile关键字的作用详情可以看一下这本篇文章
volatile关键字的作用

使用volatile关键字简单来说就是多个线程同时监听一个变量,当该变量发生变化的时候,所有的监听的线程能够感知。

public class TestSyncVolatile {// 定义一个共享变量来实现通信,它需要是volatile修饰,否则线程不能及时感知static volatile boolean notice = false;public static void main(String[] args) {List<String> list = new ArrayList<>();// 实现线程AThread threadA = new Thread(() -> {for (int i = 1; i <= 10; i++) {list.add("abc");System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if (list.size() == 4) {notice = true;}}});// 实现线程BThread threadB = new Thread(() -> {while (true) {if (notice) {System.out.println("线程B收到通知,开始执行自己的业务...");System.out.println("此时list中的元素个数为:" + list.size());break;}}});// 需要先启动线程BthreadB.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 再启动线程AthreadA.start();}}

3.消息传递

1)wait()/notify()(notifyAll())方法

wait()/notify()/notifyAll() 方法必须配合 synchronized关键字使用,关于Synchronized关键字的基本使用方法可以看一下这边文章:
Synchronized关键字的基本使用方法

wait()方法会暂时让出同步锁,以便其他正在等待此锁的线程可以得到锁并运行,其他线程调用了notify()/notifyAll()并不会立刻释放锁(notify()是随机释放单个调用当前锁的wait状态的线程,notifyAll()是释放全部调用当前锁的wait状态的线程),而是先等当前线程方法执行完才会释放锁,之后所有等待此锁的线程可以去参与获得锁的竞争,调用当前锁的 wait() 的一个或多个线程会解除 wait 状态,重新参与竞争对象锁, 得到锁的线程会继续执行。

public class TestSyncWait {public static void main(String[] args) {//定义一个锁对象Object lock = new Object();List<String> list = new ArrayList<>();// 线程AThread threadA = new Thread(() -> {for (int i = 1; i <= 10; i++) {synchronized (lock) {list.add("abc");System.out.println("线程A添加元素,此时list的size为:" + list.size());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if (list.size() == 5) {lock.notify();System.out.println("线程A还没有释放lock的同步锁");}}}});//线程BThread threadB = new Thread(() -> {while (true) {synchronized (lock) {if (list.size() != 5) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程B收到通知,开始执行自己的业务...");}}});//需要先启动线程BthreadB.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//再启动线程AthreadA.start();}}


就比如上文的示例中,先启动线程B,线程B获取lock实例的锁,但是集合中没有5个参数,所以调用wait()方法,释放lock实例的锁,此时线程A获取并执行,当集合中参数个数到达5个时,调用lock.notify()释放lock实例的单个锁,但是此时本次循环还没结束,所以继续先执行完本次循环内容之后才释放释放lock实例的单个锁,之后线程A和线程B共同竞争lock锁,有可能线程A继续抢到锁继续循环,也有可能线程B抢到锁执行他的代码,所以每次启动都可能结果不同。

2)ReentrantLock

使用ReentrantLock和wait()/notify()的大体执行逻辑相同,但还是有不少区别:

wait()/notify():
锁调用notify()方法后,会等当前方法执行完才释放锁。
配合 synchronized关键字使用,synchronized为非公平锁,notify()之后所有等待锁的线程会去竞争获取锁。

ReentrantLock:
锁调用unlock方法后会立即释放锁。
公平锁,结合Condition使用,会释放指定的对象的锁。

public class TestSyncReentrantLock {public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();Condition condition = lock.newCondition();List<String> list = new ArrayList<>();//线程AThread threadA = new Thread(() -> {lock.lock();for (int i = 1; i <= 10; i++) {list.add("abc");System.out.println("线程A添加元素,此时list的size为:" + list.size());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if (list.size() == 5) {condition.signal();lock.unlock();System.out.println("线程A还没有释放lock的同步锁");}}});//线程BThread threadB = new Thread(() -> {lock.lock();if (list.size() != 5) {try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程B收到通知,开始执行自己的业务...");lock.unlock();});threadB.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}threadA.start();}}

3)CountDownLatch

CountDownLatch使用方式与ReentrantLock类似,执行完countDown()方法后,不必等当前线程剩下方法执行完,可直接唤醒等待线程,执行其代码。

public class TestSyncCountDownLatch {public static void main(String[] args) {CountDownLatch countDownLatch = new CountDownLatch(1);List<String> list = new ArrayList<>();//线程AThread threadA = new Thread(() -> {for (int i = 1; i <= 10; i++) {list.add("abc");System.out.println("线程A添加元素,此时list的size为:" + list.size());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if (list.size() == 5) {countDownLatch.countDown();System.out.println("线程A其他业务代码");}}});//线程BThread threadB = new Thread(() -> {try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程B收到通知,开始执行自己的业务...");});//需要先启动线程BthreadB.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//再启动线程AthreadA.start();}}

4)LockSupport

LockSupport 是一种灵活的实现线程间阻塞和唤醒的工具。他可以根据线程名唤醒指定线程,唤醒之后不必等当前方法执行完,被唤醒线程可立刻执行其代码。

public class TestSyncLockSupport {public static void main(String[] args) {List<String> list = new ArrayList<>();//线程Bfinal Thread threadB = new Thread(() -> {if (list.size() != 5) {LockSupport.park();}System.out.println("线程B收到通知,开始执行自己的业务...");});//线程AThread threadA = new Thread(() -> {for (int i = 1; i <= 10; i++) {list.add("abc");System.out.println("线程A添加元素,此时list的size为:" + list.size());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if (list.size() == 5) {LockSupport.unpark(threadB);System.out.println("线程A其他业务代码...");}}});threadA.start();threadB.start();}}

线程间通信的常用方式相关推荐

  1. android 线程间通信几种方式

    1.共享变量(内存) 2.管道 3.handle机制 runOnUiThread(Runnable) view.post(Runnable)

  2. C++ 九阴真经之线程间通信(消息队列)

    消息队列是线程间通信比较常用得方式,常用于解决经典模型生产者--消费者模型线程间得通信. 本文将结束基于C++标准库实现得消息队列,可以支持任意参数类型,任务参数数量. 为了方便后续线程池.异步队列得 ...

  3. 常用并发工具类(锁和线程间通信工具类)

    常用并发工具类总结 JUC 下的常用并发工具类(锁和线程间通信工具类),主要包括 ReentrantLock.ReentrantReadWriteLock.CountDownLatch.CyclicB ...

  4. linux系统线程通信的几种方式,Linux进程间通信-线程间通信

    Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信方法:管道.消息队列.共享内存.信号量.套接口. 1.管道 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动 ...

  5. 线程间通信的几种方式

    Java线程间通信: 1:线程上下文 2:共享内存 3:IPC通信 4:套接字(Socket),不同的机器之间进行通信 另外:附注通信内容: linux常用的进程间的通讯方式 (1).管道(pipe) ...

  6. Java线程间通信-回调的实现方式

    2019独角兽企业重金招聘Python工程师标准>>> Java线程间通信-回调的实现方式 Java线程间通信是非常复杂的问题的.线程间通信问题本质上是如何将与线程相关的变量或者对象 ...

  7. android 多线程间通信,android实现线程间通信的四种常见方式

    1,通过Handler机制 主线程中定义Handler,子线程发消息,通知Handler完成UI更新,Handler对象必须定义在主线程中,如果是多个类直接互相调用,就不是很方便,需要传递conten ...

  8. 线程间通信的几种实现方式

    线程间通信的几种实现方式 首先,要短信线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的.我们来基本一道面试常见的题目来分析: 题目:有两个线程A.B,A线程向一个集合里面 ...

  9. python 线程通信的几种方式_进程间通信和线程间通信的几种方式

    进程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体:在当代 ...

最新文章

  1. nfs自动挂载报错及解决方法
  2. python 权限控制 linux_16linux的acl的控制权限的介绍
  3. Eclipse执行import命令导入maven项目时报错:Add a version or custom suffix using Name template in Advanced set...
  4. 裴蜀定理(note)
  5. Android项目开发新版本需要注意的事项
  6. 性能测试流程_流性能
  7. linux 窗口管理器_您最喜欢的Linux窗口管理器是什么?
  8. syn攻击 喝茶_如何喝茶
  9. jdbc mysql driver 6.0.2
  10. 疫情海报模板|光效显微传播大数据必备psd素材
  11. [译] 轻松发布私有 App
  12. zabbix触发器表达式详解
  13. android判断循环,android kotlin学习之路 kotlin中的分支判断与循环判断(2)
  14. 【MySQL】数据库基础_frank_fuckppt
  15. OA表单设计 案例展示
  16. python显示透明图片背景
  17. java万年历闹钟节气,闹钟 提醒 万年历 用酷石英钟搞定
  18. knex mysql 操作_mysql – 使用knex.js的我的Sql Alter表
  19. 在MMClassification中使用Swin-Transformer开始一个分类任务
  20. Java使用Spire.Pdf实现PDF添加图片水印

热门文章

  1. Java listFiles查找指定类型的文件简单做法
  2. 考研复试软件测试面试,如果考研复试采取远程面试,这4个要点一定要做好!...
  3. CF-D:Backspace
  4. 打印助手-打印暂停/开始问题原因调研记录
  5. linux制作windows光盘,在Windows上制作CentOS自动安装的光盘的教程分享
  6. 查看外文期刊的刊名、刊号、刊期
  7. BH1750 传感器实战教学 —— 驱动移植篇
  8. ML:机器学习模型可解释性之explainability和interpretability区别的简介、区别解读、案例理解之详细攻略
  9. ElasticSearch搜索引擎入门与实战
  10. 气象业务数据格式的介绍