线程间通信的常用方式

news2024/9/29 9:30:09

线程间通信的常用方式

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<>();
        // 实现线程A
        Thread 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;
                }
            }
        });
        // 实现线程B
        Thread threadB = new Thread(() -> {
            while (true) {
                if (notice) {
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                    System.out.println("此时list中的元素个数为:" + list.size());
                    break;
                }
            }
        });
        // 需要先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 再启动线程A
        threadA.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<>();
        // 线程A
        Thread 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的同步锁");
                    }
                }
            }
        });
        //线程B
        Thread threadB = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (list.size() != 5) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                }
            }
        });
        //需要先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //再启动线程A
        threadA.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<>();
        //线程A
        Thread 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的同步锁");
                }
            }
        });
        //线程B
        Thread 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<>();
        //线程A
        Thread 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其他业务代码");
                }
            }
        });
        //线程B
        Thread threadB = new Thread(() -> {
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程B收到通知,开始执行自己的业务...");
        });
        //需要先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //再启动线程A
        threadA.start();
    }

}

在这里插入图片描述

4)LockSupport

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

public class TestSyncLockSupport {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        //线程B
        final Thread threadB = new Thread(() -> {
            if (list.size() != 5) {
                LockSupport.park();
            }
            System.out.println("线程B收到通知,开始执行自己的业务...");
        });
        //线程A
        Thread 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();
    }

}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

博客系统测试用例

博客系统测试用例目录博客系统测试用例博客系统删除功能测试用例 (判定表)提交BUG 1测试用例 1测试用例 2提交BUG提交BUG 2博客系统测试用例 博客系统删除功能测试用例 (判定表) # 首先确定输入条件与输出条件 输入条件: 博客作者, 非博客作者, 点击删除博客输出条件: 删除成…

web移动端:rem适配布局(重点)

目录 1. rem基础 2.媒体查询 2.1 媒体查询的概念 2.2 语法规范 2.2.1 2.2.2 关键字 2.2.3 媒体特性 2.2 根据页面宽度改变颜色 2.3 媒体查询rem 实现元素动态大小变化 2.4 引入资源&#xff08;理解&#xff09; 2.4.1 语法规范 3. less基础 3.1 css弊端 3.2 less介绍…

基于SuperPoint与SuperGlue实现图像配准

基于SuperPoint与SuperGlue实现图像配准&#xff0c;项目地址https://github.com/magicleap/SuperGluePretrainedNetwork&#xff0c;使用到了特殊算子grid_sample&#xff0c;在转onnx时要求opset_version为16及以上&#xff08;即pytorch版本为1.9以上&#xff09;。SuperPoi…

计讯物联污染源自动监控系统,坚守“绿水青山就是金山银山”

近年来&#xff0c;“绿水青山就是金山银山”的理念在全国各地落地生根&#xff0c;各大城市积极构建环境监测体系&#xff0c;旨在让生态文明成色更足&#xff0c;绿色发展底色更亮。计讯物联污染源自动监控系统作为生态环境部门监督企业排污的“火眼金睛”&#xff0c;充分运…

apifox持续集成+java+企微机器人+xxljob定时推送

总览&#xff1a; apifox做接口测试后&#xff0c;把用例合并组装成测试套件&#xff0c;然后apifox-cli通过终端命令实现把套件执行后&#xff0c;输出本地文件的测试报告html或json。本地解析后拿到有用的解决通过定时执行推送到企微群里。 然后把html一起推到群里。 这个…

【Spark分布式内存计算框架——Spark SQL】8. Shuffle 分区数目、Dataset(上)

4.4 Shuffle 分区数目 运行上述程序时&#xff0c;查看WEB UI监控页面发现&#xff0c;某个Stage中有200个Task任务&#xff0c;也就是说RDD有200分区Partition。 原因&#xff1a;在SparkSQL中当Job中产生Shuffle时&#xff0c;默认的分区数&#xff08;spark.sql.shuffle.p…

基于STM32采用CS创世 SD NAND(贴片SD卡)完成FATFS文件系统移植与测试

一、前言 在STM32项目开发中&#xff0c;经常会用到存储芯片存储数据。 比如&#xff1a;关机时保存机器运行过程中的状态数据&#xff0c;上电再从存储芯片里读取数据恢复&#xff1b;在存储芯片里也会存放很多资源文件。比如&#xff0c;开机音乐&#xff0c;界面上的菜单图…

Selenium + python自动化测试环境搭建

selenium 是一个web的自动化测试工具&#xff0c;不少学习功能自动化的同学开始首选selenium &#xff0c;相因为它相比QTP有诸多有点&#xff1a; 免费&#xff0c;也不用再为破解QTP而大伤脑筋 小巧&#xff0c;对于不同的语言它只是一个包而已&#xff0c;而QTP需要下载安…

JSON字符串解析

目录 依赖 方法 示例 判断JSON是否合格 依赖 方法 JSON.parseObject() JSON.parseArray() 示例 Data public class OrderVo {public String name;public Integer price;public Integer count; } JSON数据 { "name": "苹果手机", "pric…

BIT.8_Linux 多线程

目录Linux线程概念什么是线程线程的优点线程的缺点线程异常线程用途Linux进程VS线程进程和线程总结Linux线程控制POSIX线程库创建线程线程ID及进程地址空间布局进程和线程ID区别内核层面&#xff1a;pid & tgid线程终止线程等待__thread 和 pthread_self()分离线程Linux线程…

《爆肝整理》保姆级系列教程python接口自动化(十七)--Json 数据处理---一次爬坑记(详解)

简介 有些 post 的请求参数是 json 格式的&#xff0c;这个前面发送post 请求里面提到过&#xff0c;需要导入 json模块处理。现在企业公司一般常见的接口因为json数据容易处理&#xff0c;所以绝大多数返回数据也是 json 格式的&#xff0c;我们在做判断时候&#xff0c;往往只…

Guava常用工具类总结

-“Null的含糊语义让人很不舒服。Null很少可以明确地表示某种语义&#xff0c;例如&#xff0c;Map.get(key)返回Null时&#xff0c;可能表示map中的值是null&#xff0c;亦或map中没有key对应的值。Null可以表示失败、成功或几乎任何情况。使用Null以外的特定值&#xff0c;会…

每日学术速递2.17

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.LG 1.Decoupled Model Schedule for Deep Learning Training 标题&#xff1a;深度学习训练的解耦模型时间表 作者&#xff1a;Hongzheng Chen, Cody Hao Yu, Shuai Zheng, Zhen Zhang,…

快速识别台式机的内存条

拿上一根内存条&#xff0c;让一个喜欢IT的识别一下&#xff0c;很多人不一定能说出点内容。 这很正常&#xff0c;IT细分领域太多了&#xff0c;很多搞IT的包括写代码的人可能都没有接触内存条。 硬件的集成度随着硬件技术的提升越来越高&#xff0c;成本也下来了&#xff0c;…

支付宝支付详细流程

1、二维码的生成二维码生成坐标 <!-- zxing生成二维码 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.3</version></dependency><dependency><groupId>co…

nvm 控制 node版本

nvm 官网 https://nvm.uihtm.com/ 1、卸掉nodejs&#xff0c;根据官网操作 2、如果之前安装过的nodejs,且安装的目录改变了&#xff0c;需重新配置系统环境 第一步&#xff1a;打开此电脑 > 右键属性 > 高级系统设置 > 环境变量 第二步&#xff1a; 在系统变量中选中…

新手健身准备哪些物品,健身必备蓝牙运动耳机分享

第一次运动健身应该准备什么&#xff1f;运动耳机是一款必备的装备&#xff0c;可以让我们坚持运动的动力源泉&#xff0c;在健身当中远离枯燥乏味&#xff0c;有音乐的加持下健身能够让我们更具动力&#xff0c;有哪些值得入手的蓝牙运动耳机分享呢&#xff1f;看看下面这写分…

Java反射概述

2 反射 2.1 反射概述 Java反射机制&#xff1a;是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展 2.2 反射获取Class类的对象 …

企业的知识文档管理系统需要注重什么?安全和共享能力很重要!

编者按&#xff1a;本文指出了企业的文档管理系统比较注重的能力&#xff0c;并从知识共享和文档安全两方面介绍了老厂商天翎是如何在这块实践的。关键词&#xff1a;知识共享&#xff0c;知识安全&#xff0c;标签分类&#xff0c;智能检索&#xff0c;资料分享&#xff0c;在…

element ui 下拉菜单组件 结合springboot 实现省市区简易三级联动 动态查询 并修改地点的省市区

目录 前言&#xff1a; 一.数据库表结构&#xff1a; 二.下拉菜单组件 2.1 效果展示 2.2下拉菜单的组件代码&#xff1a; 前言&#xff1a; 本篇博客&#xff0c;通过官网的学习&#xff0c;实现下拉菜单动态数据的传递与点击事件&#xff0c;如果只是按部就班的按照官网来…