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

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

一、等待/通知机制

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();
        // 将输出流和输入流进行连接,否则在使用时会抛出IOException
        out.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;
        }

        @Override
        public 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()方法获取到原先设置的值。

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

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

相关文章

Windows10安装PCL1.14.0及点云配准

一、下载visual studio2022 下载网址&#xff1a;Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器 (microsoft.com) 安装的时候选择"使用C的桌面开发“&#xff0c;同时可以修改文件路径&#xff0c;可以放在D盘。修改文件路径的时候&#xff0c;共享组件、…

Web APIs 2 事件

Web APIs 2 事件 事件监听案例&#xff1a;广告关闭案例&#xff1a;随机问答 事件监听版本事件类型案例&#xff1a;轮播图完整焦点事件键盘事件输入事件案例&#xff1a;评论字数统计 事件对象获取事件对象事件对象常用属性案例&#xff1a;评论回车发布 环境对象this回调函数…

电脑怎么扫描纸质文件?6步轻松完成扫描!

“由于工作的需要&#xff0c;我要将部分纸质文件扫描到电脑上&#xff0c;不知道应该怎么操作会更方便呢&#xff1f;希望大家给我出出主意。” 随着科技的进步&#xff0c;电脑已经成为了我们日常生活和工作中不可或缺的工具。除了传统的文字处理和数据处理&#xff0c;电脑还…

【漏洞库】O2OA系统

O2OA invoke 后台远程命令执行漏洞 CNVD-2020-18740 漏洞描述 O2OA是一款开源免费的企业及团队办公平台,提供门户管理、流程管理、信息管理、数据管理四大平台,集工作汇报、项目协作、移动OA、文档分享、流程审批、数据协作等众多功能,满足企业各类管理和协作需求。 O2OA系…

拍摄的视频怎么做二维码?视频在线转二维码的技巧

现在学校经常会将学生日常的拍摄的短片做成二维码之后展示给其他人&#xff0c;其他人可以通过扫描二维码来查看个人表现的视频&#xff0c;有些活动视频也会用视频二维码的方式来传播。那么视频二维码制作的方法及步骤是什么样的呢&#xff1f;下面就让小编通过本文来给大家介…

力扣 121. 买卖股票的最佳时机

题目来源&#xff1a;https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/ 好久没写代码了&#xff0c;啥啥都忘了 C题解1&#xff1a;贪心算法。&#xff08;来源代码随想录&#xff09; 因为股票就买卖一次&#xff0c;那么贪心的想法很自然就是取…

读写锁ReentrantReadWriteLockStampLock详解

传送门&#xff1a;深入理解AQS独占锁之ReentrantLock源码分析 目录 读写锁介绍 ReentrantReadWriteLock介绍 ReentrantReadWriteLock的使用 应用场景 锁降级 读写锁设计思路 StampedLock介绍 StampedLock的使用 演示乐观读 在缓存中的应用 使用场景和注意事…

033-安全开发-JavaEE应用SQL预编译Filter过滤器Listener监听器访问控制

033-安全开发-JavaEE应用&SQL预编译&Filter过滤器&Listener监听器&访问控制 #知识点&#xff1a; 1、JavaEE-JDBC-SQL预编译 2、JavaEE-HTTP-Filter过滤器 3、JavaEE-对象域-Listen监听器 演示案例&#xff1a; ➢JavaEE-预编译-SQL ➢JavaEE-过滤器-Filter ➢…

python Cloudflare 批量关闭IPv6兼容性脚本

Cloudflare免费版控制台不给关IPv6&#xff0c;需要使用API关闭&#xff0c;先从我的个人资料里面申请API令牌&#xff0c;再执行脚本 import requests import jsonheaders {X-Auth-Email:cloudflare登入账户, #输入登入账户的邮箱X-Auth-Key: Global API Key, #输入上图申请…

计算机自顶向下 Wireshark labs——DNS

如本文第2.4节所述&#xff0c;域名系统(DNS)将主机名转换为IP地址&#xff0c;在互联网基础设施中发挥着关键作用。在本实验中&#xff0c;我们将仔细研究DNS的客户端。回想一下&#xff0c;客户端在DNS中的角色相对简单—客户端向其本地DNS服务器发送查询&#xff0c;并收到响…

探索设计模式的魅力:从单一继承到组合模式-软件设计的演变与未来

设计模式专栏&#xff1a;http://t.csdnimg.cn/nolNS 在面对层次结构和树状数据结构的软件设计任务时&#xff0c;我们如何优雅地处理单个对象与组合对象的一致性问题&#xff1f;组合模式&#xff08;Composite Pattern&#xff09;为此提供了一种简洁高效的解决方案。通过本…

使用 PyTorch 构建 NLP 聊天机器人

一、说明 聊天机器人提供自动对话&#xff0c;可以帮助用户完成任务或寻求信息。随着深度学习的最新进展&#xff0c;聊天机器人正变得越来越具有对话性和实用性。这个全面的教程将利用 PyTorch 和 Python 从头开始构建聊天机器人&#xff0c;涵盖模型架构、数据准备、训练循环…

【动态规划】【状态压缩】【2次选择】【广度搜索】1494. 并行课程 II

作者推荐 视频算法专题 本文涉及知识点 动态规划汇总 状态压缩 广度优先搜索 LeetCode1494. 并行课程 II 给你一个整数 n 表示某所大学里课程的数目&#xff0c;编号为 1 到 n &#xff0c;数组 relations 中&#xff0c; relations[i] [xi, yi] 表示一个先修课的关系&am…

nginx slice模块的使用和源码分析

文章目录 1. 为什么需要ngx_http_slice_module2. 配置指令3. 加载模块4. 源码分析4.1 指令分析4.2 模块初始化4.3 slice模块的上下文4.2 $slice_range字段值获取4.3 http header过滤处理4.4 http body过滤处理5 测试和验证 1. 为什么需要ngx_http_slice_module 顾名思义&#…

配置Jenkins自动构建打包项目

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 需求说明 1、给A项目配置jenkins每2小时无条件自动构建一次&#xff0c;无论是否有代码提交。 2、给B项目配置jenkins每15分钟检…

FPGA高端项目:IMX327 MIPI 视频解码 USB3.0 UVC 输出,提供FPGA开发板+2套工程源码+技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的 MIPI 编解码方案 3、本 MIPI CSI-RX IP 介绍4、个人 FPGA高端图像处理开发板简介5、详细设计方案设计原理框图IMX327 及其配置MIPI CSI RX图像 ISP 处理图像缓存UVC 时序USB3.0输出架构 6、vivado工程详解FPGA逻辑设计 7、工…

ChatGPT辅助编程,一次有益的尝试

如果大家想学习PCIe&#xff0c;搜索网上的信息&#xff0c;大概率会看到chinaaet上Felix的PCIe扫盲系列的博文 Felix-PCIe扫盲 每次看这个系列博文的时候&#xff0c;我都在想有没有什么方法可以把这个系列的博文都保存到一个pdf文件中&#xff0c;这样方便阅读。于是有了下…

异地办公必不可缺的远程控制软件,原理到底是什么?

目录 引言远程桌面连接软件的作用与重要性 基本概念与架构客户端-服务器模型网络通信协议 核心技术组件图形界面捕获与传输输入转发会话管理 性能优化策略带宽优化延迟优化 引言 远程桌面连接软件的作用与重要性 在当今这个高度数字化和网络化的时代&#xff0c;远程桌面连接软…

R语言学习case10:ggplot基础画图Parallel Coordinate Plot 平行坐标图

step1: 导入ggplot2库文件 library(ggplot2)step2&#xff1a;带入自带的iris数据集 iris <- datasets::irisstep3&#xff1a;查看数据信息 dim(iris)维度为 [150,5] head(iris)查看数据前6行的信息 step4&#xff1a;利用ggplot工具包绘图 plot5 <- ggparcoord(…

Linux目录:traceroute命令

目录 traceroute1、简介2、探测原理3、traceroute说明4、实例设置每跳探测数设置跳数探测包使用的基本UDP端口设置6789把对外发探测包的等待响应时间设置为3秒 总结 traceroute 1、简介 traceroute的主要功能是跟踪从IP网络发送到指定主机经过的网关的工具。它利用IP协议的生…