多线程生命周期与通信(二)通信

线程自启动时,就拥有了自己的栈空间。然后会一直运行直到结束。多线程的目的是多条线程执行不同的逻辑业务从而能够提升业务整体的响应速度,如果线程仅仅是孤零零的执行,不同的逻辑业务就不能最终汇聚成一个完整的业务那么多线程也就失去了意义,这就是为什么要有线程间通信的存在。实现线程之间的通信有以下方法:

一、等待/通知机制

1、介绍

一个线程修改一个对象的值,另一个线程感知变化。线程A调用对象O的wait()进入等待状态,另一个线程B调用对象O的notify()或者 notifuAll()方法,线程A 收到通知后从对象O 的wait()方法返回,执行后续操作。两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

(1)wait();当前线程暂停,等待notify()来唤醒(释放资源)。 

(2)使用锁对象的notify()方法可以将正在等待的线程唤醒,但是同时有多个线程都处于等待状态,notify()只是随机唤醒一个。
   注:唤醒后的进程进入就绪态,而不是进入运行态。虽然线程被唤醒,但只有当前线程放弃对同步锁对象的锁定,被唤醒的线程才可能执行被执行

(3)notifyAll()
唤醒在此同步锁对象上等待的所有线程。同上,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程

2、规则 
2.1、等待方遵循原则

(1)获取对象的锁。

(2)如果条件不满足,那么调用对象的 wait()方法,被通知后仍要检查条件。

(3)条件满足则执行对应的逻辑。

对应的伪代码如下:

synchronized(对象) { while(条件不满足) {  对象.wait();  }  对应的处理逻辑 
} 
2.2、通知方遵循原则 

(1)获得对象的锁。

(2)改变条件

(3)通知所有等待在对象上的线程。

synchronized(对象){  改变条件  对象.notifyAll(); 
} 
 3、demo:
public class Consume {private static final Logger logger = LoggerFactory.getLogger(Consume.class);private final Object lockValue;public Consume(Object object) {this.lockValue = object;}/*** 生产者赋值*/public void getValue() {synchronized (lockValue) {if (ObjectUtils.isEmpty(ProductConsumeValue.value)) {try {lockValue.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}logger.info("Consume :{}", ProductConsumeValue.value);ProductConsumeValue.value = "";lockValue.notifyAll();}}
}

public class Product {private static final Logger logger = LoggerFactory.getLogger(Consume.class);private Object lockValue;public Product(Object lockValue) {this.lockValue = lockValue;}/*** 生产者赋值*/public void setValue() {synchronized (lockValue) {if (!ObjectUtils.isEmpty(ProductConsumeValue.value)) {try {lockValue.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}ProductConsumeValue.value = System.currentTimeMillis() + "_" + System.nanoTime();logger.info("Product :{}", ProductConsumeValue.value);lockValue.notify();}}
}
public static void main(String[] args) {String value = "";Product product = new Product(value);Consume consume = new Consume(value);ProductThread productThread = new ProductThread(product);ConsumerThread consumerThread = new ConsumerThread(consume);productThread.start();consumerThread.start();
}

二、等待超时模式 

1、介绍

调用一个方法时等待一段时间(一般来说是给定一个时间段),如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超时返回默认结果。等待超时模式就是在等待/通知范式基础上增加了超时控制,这使得该模式相比原有范式更具有灵活性,因为即使方法执行时间过长,也不会“永久”阻塞调用者,而是会按照调用者的要求“按时”返回。

2、实现

等待/通知的经典范式,即加锁、条件循环和处理逻辑3个步骤,而这种范式无法做到超时等待。超时等待的加入,在等待通知范式上做出改动:假设超时时间段是T,那么可以推断出在当前时间now+T之后就会超时定义如下变量:等待持续时间:REMAINING=T;超时时间:FUTURE=now+T。这时仅需要wait(REMAINING)即可,在wait(REMAINING)返回之后会将执行:REMAINING=FUTURE–now。如果REMAINING小于等于0,表示已经超时,直接退出,否则将继续执行wait(REMAINING)。

三、管道输入/输出流

1、介绍

管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它 主要用于线程之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4 种具体实现:PipedOutputStream、PipedInputStream、PipedReader 和 PipedWriter,前两种面向字节,而后两种面向字符。

2、demo
public class Piped {public static void main(String[] args) throws Exception {PipedWriter out = new PipedWriter();PipedReader in = new PipedReader();// 将输出流和输入流进行连接,否则在使用时会抛出IOExceptionout.connect(in);Thread printThread = new Thread(new Print(in), "PrintThread");printThread.start();int receive = 0;try {while ((receive = System.in.read()) != -1) {out.write(receive);}} finally {out.close();}}static class Print implements Runnable {private PipedReader in;public Print(PipedReader in) {this.in = in;}public void run() {int receive = 0;try {while ((receive = in.read()) != -1) {System.out.print((char) receive);}} catch (IOException ex) {}}}

从system.in即控制台中读入一个字符,转为int,将int值写入到管道输出流中,既然输入流与输出流已经连接,那么在输入流就会读取到int值,转为char就能输出。对于Piped类型的流,必须先进行绑定,也就是调用connect()方法,如果没有将输入/输出流绑定起来,对于该流的访问将会抛出异常。

四、Thread.join()

1、介绍

当前线程暂停,等待加入的线程运行结束,当前线程继续执行。

如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。如果一个线程 A 执行了 thread.join()语句,其含义是:当前线程 A 等待 thread 线程终止之后才从 thread.join()返回。

2、原理

查看源码可以看到底层与等待/通知机制范式一致,即加锁、循环、处理逻辑三个步骤。

3、demo
public class ThreadJoinTest {private static final Logger logger = LoggerFactory.getLogger(ThreadJoinTest.class);/*** 如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到 join() 方法了。** @param args args* @throws InterruptedException 中断异常*/public static void main(String[] args) throws InterruptedException {Thread currentThread = Thread.currentThread();for (int i = 0; i < 10; i++) {JoinThreadTest joinTestTread = new JoinThreadTest(currentThread);Thread thread = new Thread(joinTestTread, "线程 " + i);thread.start();currentThread = thread;}Thread.sleep(5000);}private static class JoinThreadTest implements Runnable {private final Thread thread;private JoinThreadTest(Thread currentThread) {thread = currentThread;}@Overridepublic void run() {try {thread.join();} catch (InterruptedException e) {throw new RuntimeException(e);}logger.info("当前线程:{}", Thread.currentThread().getName());}}
}

五、使用ThreadLocal 

1、介绍

在线程不安全问题的解决方法中也提到过ThreadLocal ,ThreadLocal 即线程变量,每个线程可以根据一个 ThreadLocal 对象查询到绑定在这个线程上的一个值。可以通过 set(T)方法来设置一个值,在当前线程下再通过 get()方法获取到原先设置的值。

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

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

相关文章

02-Web应用_架构构建_漏洞_HTTP数据包_代理服务器

Web应用_架构构建_漏洞_HTTP数据包_代理服务器 一、网站搭建前置知识1.1 域名1.2、子域名1.3、DNS二、web应用环境架构类三、web应用安全漏洞分类四、web请求返回过程数据包 五、演示案例5.1、架构-Web应用搭建-域名源码解析5.2、请求包-新闻回帖点赞-重放数据包5.3、请求包-移…

2024.2.4 awd总结

防御阶段 感觉打了几次awd&#xff0c;前面阶段还算比较熟练 1.ssh连接 靶机登录 修改密码 [root8 ~]# passwd Changing password for user root. New password: Retype new password: 2.xftp连接 备份网站源码 我觉得这步还是非常重要的&#xff0c;万一后面被删站。。…

Java-并发高频面试题-2

接着之前的Java-并发高频面试题 7. synchronized的实现原理是怎么样的&#xff1f; 首先我们要知道synchronized它是解决线程安全问题的一种方式&#xff0c;而具体是怎么解决的呢&#xff1f;主要是通过加锁的方式来解决 在底层实现上来看 是通过 monitorenter、monitorexit…

零售新业态,让老牧区焕发新生命

敦煌老马一声魔性“浇给”勾起了无数人对羊肉的食欲&#xff0c;而当大家集体涌入餐厅或者在网上下单&#xff0c;都想要尝一尝网红同款的时候&#xff0c;可能并没有想过这样一个问题——为什么在今天&#xff0c;即便是远离牧区的现代大城市&#xff0c;草原羊肉却一样能触手…

2、ChatGPT 在数据科学中的应用

ChatGPT 在数据科学中的应用 ChatGPT 可以成为数据科学家的绝佳工具。以下是我所了解到的关于它擅长的地方和不那么擅长的地方。 我从使用 ChatGPT 中学到了一个教训。它在数据科学中非常有帮助,但你必须仔细检查它输出的所有内容。它非常适合某些任务,并且可以非常快速准确…

【Iceberg学习二】Branch和Tag在Iceberg中的应用

Iceberg 表元数据保持一个快照日志&#xff0c;记录了对表所做的更改。快照在 Iceberg 中至关重要&#xff0c;因为它们是读者隔离和时间旅行查询的基础。为了控制元数据大小和存储成本&#xff0c;Iceberg 提供了快照生命周期管理程序&#xff0c;如 expire_snapshots&#xf…

瑞萨RA6M3开发实践指南-UART实践

1.背景说明 本文是参考瑞萨RA6M3开发实践指南文章教程&#xff0c;基于瑞萨HMI-Board BSP :1.1.1 版本 RT-Thread 5.0.1 版本操作步骤进行记录&#xff0c;整理成的文档。 1.1 本章内容 使用RT-Thread Studio创建开发板的程序&#xff0c;编写UART的程序&#xff0c;实现串口…

微软Windows生态是怎么打造成功的?

&#xff08;1&#xff09;2015年Windows10&#xff1a;兼容性 我不得不再次佩服一下微软&#xff0c;Windows10是2015年出品的&#xff0c;但是仍然能正常运行绝大多数的Windows95软件&#xff0c;不用做任何的适配修改&#xff0c;连重新编译都不用&#xff0c;运行照样正常。…

arcgis各种版本下载

arcgic 下载&#xff01;&#xff01;&#xff01; ArcGIS是一款地理信息系统软件&#xff0c;由美国Esri公司开发。它提供了一系列完整的GIS功能&#xff0c;包括地图制作、空间数据管理、空间分析、空间信息整合、发布与共享等。ArcGIS是一个可扩展的GIS平台&#xff0c;提供…

机器学习数据预处理方法(数据重编码) ##2

文章目录 [TOC]基于Kaggle电信用户流失案例数据&#xff08;可在官网进行下载&#xff09;一、离散字段的数据重编码1.OrdinalEncoder自然数排序2.OneHotEncoder独热编码3.ColumnTransformer转化流水线 二、连续字段的特征变换1.标准化&#xff08;Standardization&#xff09;…

科研绘图-半小提琴图-

文章目录 前言1.软件安装-Origin 20222.绘制半小提琴图3.绘制径向条形图 前言 本文叙述记录的是一些科研绘图的实现方法&#xff0c;具体介绍从软件安装到实现图表绘制的详细过程。 1.软件安装-Origin 2022 Origin是一款具有丰富绘图功能的科研绘图软件&#xff0c;安装过程…

【LangChain-04】利用权重和偏差跟踪和检查LangChain代理的提示

利用权重和偏差跟踪和检查LangChain代理的提示 一、说明 考虑到&#xff08;生成&#xff09;人工智能空间&#xff0c;&#xff08;自主&#xff09;代理现在无处不在&#xff01;除了更强大且幸运的是开放的大型语言模型&#xff08;LLM&#xff09;之外&#xff0c;LangCh…