JUC高并发编程3:线程间通信

1 线程间通信

线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析

场景:两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信

1.1 synchronized方法

// 第一步 创建资源类,定义属性和操作方法
class Share{//初始值private int number = 0;// +1的方法public synchronized void incr() throws InterruptedException {// 判断while(number != 0) {this.wait();}// 干活number++;System.out.println(Thread.currentThread().getName() + " :: " + number);//通知其他线程this.notifyAll();}// -1的方法public synchronized void decr() throws InterruptedException {// 判断while(number != 1) {this.wait();}// 干活number--;System.out.println(Thread.currentThread().getName() + " :: " + number);//通知其他线程this.notifyAll();}}
public class ThreadDemo1 {// 第三步创建多个线程,调用资源类的操作方法public static void main(String[] args) {Share share = new Share();// 创建线程new Thread(()->{for (int i = 0; i < 10; i++) {try {share.incr();//+1} catch (InterruptedException e) {throw new RuntimeException(e);}}},"AA").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {share.decr(); //-1} catch (InterruptedException e) {throw new RuntimeException(e);}}},"BB").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {share.incr();//+1} catch (InterruptedException e) {throw new RuntimeException(e);}}},"CC").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {share.decr(); //-1} catch (InterruptedException e) {throw new RuntimeException(e);}}},"DD").start();}
}

1.2 Lock方案

// 第一步 创建资源类,定义属性和操作方法
class Share{//初始值private int number = 0;//创建Lockprivate Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();// +1public void incr() throws InterruptedException {//上锁lock.lock();try {//判断while (number != 0){condition.await();}//干活number++;System.out.println(Thread.currentThread().getName() + " :: " + number);//通知condition.signalAll();}finally {// 解锁lock.unlock();}}// -1public void decr() throws InterruptedException {//上锁lock.lock();try {//判断while (number != 1){condition.await();}//干活number--;System.out.println(Thread.currentThread().getName() + " :: " + number);//通知condition.signalAll();}finally {// 解锁lock.unlock();}}
}
public class ThreadDemo2 {// 第三步创建多个线程,调用资源类的操作方法public static void main(String[] args) {Share share = new Share();new Thread(()->{for (int i = 0; i < 10; i++) {try {share.incr();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"AA").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {share.decr();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"BB").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {share.incr();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"CC").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {share.decr();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"DD").start();}
}

1.3 虚假唤醒问题

在Java中,线程间通信通常使用 Object 类的 wait()notify()notifyAll() 方法来实现。这些方法与条件变量类似,但同样可能会出现虚假唤醒(Spurious Wakeup)的问题。

1.3.1 什么是虚假唤醒?

虚假唤醒是指一个线程在没有被显式通知的情况下被唤醒。换句话说,即使没有调用 notify()notifyAll() 方法,等待的线程也可能被唤醒。这种现象在某些操作系统或线程库中是允许的,因为它可以简化某些实现。

1.3.2 为什么会出现虚假唤醒?

虚假唤醒的原因可能包括:

  1. 操作系统调度:操作系统可能在某些情况下唤醒线程,即使没有显式的通知。
  2. 多核处理器:在多核处理器上,线程可能在不同的核心上运行,导致某些同步机制不完全可靠。
  3. Java 实现:Java 的线程库实现可能会允许虚假唤醒,以提高性能或简化实现。

1.3.3 如何处理虚假唤醒?

为了避免虚假唤醒带来的问题,通常的做法是在循环中检查条件变量。这样,即使线程被虚假唤醒,它也会在循环中重新检查条件,如果条件不满足,线程会继续等待。

以下是一个使用 wait()notifyAll() 的典型示例:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class SpuriousWakeupExample {private static final Lock lock = new ReentrantLock();private static final Condition condition = lock.newCondition();private static boolean ready = false;public static void main(String[] args) throws InterruptedException {Thread workerThread = new Thread(new Worker());workerThread.start();// 主线程设置条件并通知Thread.sleep(1000); // 模拟一些工作lock.lock();try {ready = true;condition.signalAll(); // 通知等待的线程} finally {lock.unlock();}workerThread.join();}static class Worker implements Runnable {@Overridepublic void run() {lock.lock();try {// 在循环中检查条件while (!ready) {condition.await(); // 等待条件满足}// 条件满足,执行工作System.out.println("Worker thread is processing data");} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {lock.unlock();}}}
}

1.3.4 关键点

  1. 循环检查条件:在 await() 方法周围使用 while 循环来检查条件,而不是 if 语句。这样可以确保即使线程被虚假唤醒,它也会重新检查条件。
  2. 使用 LockCondition:在示例中使用了 ReentrantLockCondition,这是 Java 中更灵活的同步机制。你也可以使用 synchronized 关键字和 Objectwait()notify()notifyAll() 方法,但原理是相同的。
  3. 处理中断:在 await() 方法中捕获 InterruptedException,并处理线程中断的情况。

通过这种方式,可以有效避免虚假唤醒带来的问题,确保线程在条件真正满足时才继续执行。

1.4 多线程编程步骤

  • 第一步创建资源类,在资源类创建属性和操作方法

  • 第二步在资源类操作方法

    • 判断
    • 干活
    • 通知
  • 第三步创建多个线程,调用资源类的操作方法

  • 第四步防止虚假唤醒问题

2 线程间定制化通信

让线程按照指定顺序进行通信。

2.1 案例介绍

启动三个线程,按照如下要求执行:
AA线程打印 5 次 A,BB 线程打印 10 次 B,CC 线程打印 15 次 C,按照此顺序循环 10 轮

2.2 流程分析

在这里插入图片描述

2.3 代码实现

// 第一步 创建资源类,定义属性和操作方法
class ShareResourse{// 定义标识位private int flag = 1; // 1:AA; 2:BB; 3:CC//创建Lockprivate Lock lock = new ReentrantLock();//创建三个conditionprivate Condition c1 = lock.newCondition();private Condition c2 = lock.newCondition();private Condition c3 = lock.newCondition();//打印5次,参数第几轮public void print5(int loop) throws InterruptedException {//上锁lock.lock();try{while (flag != 1){//等待c1.await();}//干活for (int i = 0; i <= 5; i++) {System.out.println(Thread.currentThread().getName() + " :: " + i +", 轮数:"+ loop);}//通知flag = 2; //修改标识位2c2.signal();//通知BB线程}finally {// 释放锁lock.unlock();}}//打印10次,参数第几轮public void print10(int loop) throws InterruptedException {//上锁lock.lock();try{while (flag != 2){//等待c2.await();}//干活for (int i = 0; i <= 10; i++) {System.out.println(Thread.currentThread().getName() + " :: " + i +", 轮数:"+ loop);}//通知flag = 3; //修改标识位3c3.signal();//通知CC线程}finally {// 释放锁lock.unlock();}}//打印15次,参数第几轮public void print15(int loop) throws InterruptedException {//上锁lock.lock();try{while (flag != 3){//等待c3.await();}//干活for (int i = 0; i <= 15; i++) {System.out.println(Thread.currentThread().getName() + " :: " + i +", 轮数:"+ loop);}//通知flag = 1; //修改标识位1c1.signal();//通知AA线程}finally {// 释放锁lock.unlock();}}
}
public class ThreadDemo3 {// 第三步创建多个线程,调用资源类的操作方法public static void main(String[] args) {ShareResourse shareResourse = new ShareResourse();new Thread(()->{for (int i = 0; i <= 10; i++) {try {shareResourse.print5(i);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"AA").start();new Thread(()->{for (int i = 0; i <= 10; i++) {try {shareResourse.print10(i);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"BB").start();new Thread(()->{for (int i = 0; i <= 10; i++) {try {shareResourse.print15(i);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"CC").start();}
}

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的class对象。
对于同步方法块,锁是synchonized括号里配置的对象

3 思维导图

在这里插入图片描述

4 参考链接

【【尚硅谷】大厂必备技术之JUC并发编程】

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

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

相关文章

使用离火插件yoloV8数据标注,模型训练

1. 启动 2.相关配置 2.1 data.yaml path: D:/yolo-tool/yaunshen-yolov8/YOLOv8ys/YOLOv8-CUDA10.2/1/datasets/ceshi001 train: images val: images names: [蔡徐坤,篮球] 2.2 cfg.yaml # Ultralytics YOLOv8, GPL-3.0 license # Default training settings and hyp…

为什么你应该将你的营销材料本地化为俄语:释放新的机会

在当今高度互联的世界中&#xff0c;企业不断寻求新市场以扩大其全球足迹。一个经常被忽视但充满未开发潜力的市场是俄罗斯。全球有超过2.6亿俄语使用者&#xff0c;将您的营销材料翻译成俄语并本地化不仅是一个明智之举&#xff0c;也是迈向强大经济集团和获得竞争优势的重要一…

Docker安装nacos最新版本(图文教程)

Nacos(Naming And Configuration Service)是阿里巴巴开源的一个动态服务发现、配置管理和服务管理平台。Nacos 提供了一套简单易用的服务发现、配置管理、动态 DNS 服务以及服务健康检查的解决方案,广泛应用于微服务架构中。 一、拉取镜像 docker pull nacos/nacos-server:…

@Lazy注解原理

目录 Lazy作用在类上Lazy注解作用在字段上Lazy注解标记的字段或方法中的参数何时触发加载AOP代理中的TargetSource对象为什么使用了 Lazy 之后&#xff0c;就能解决循环依赖问题&#xff0c;正常启动了呢&#xff1f;案例Resource对Lazy注入的处理 参考&#xff1a; https://b…

微服务——服务保护(Sentinel)(一)

1.雪崩问题 级联失败或雪崩问题指的是在微服务架构中&#xff0c;由于服务间的相互依赖和调用&#xff0c;当一个服务出现故障时&#xff0c;会引起调用它的服务也出现故障&#xff0c;进而引发整个调用链路的多个服务都出现故障&#xff0c;最终导致整个系统崩溃的现象。 产生…

【笔记】Dynamic Taint Analysis 动态污点分析

Dynamic Taint Analysis 动态污点分析 什么是动态污点分析&#xff1f;为什么要搞动态污点分析&#xff1f; “污点”指的是什么&#xff1f; DTA中的“污点”指代的是不可信的输入&#xff0c;比如用户输入、网络请求、文件数据等。比方说&#xff0c;如果把程序看作一个城市&…

使用 Visily.ai 进行应用界面设计

在现代应用开发中&#xff0c;快速创建高保真线框图和原型是一个巨大的优势。Visily.ai 是一个利用人工智能帮助你实现这一目标的在线工具。本文将介绍如何使用 Visily.ai 进行应用界面设计。 什么是 Visily.ai&#xff1f; Visily.ai 是一个 AI 驱动的 UI 设计工具&#xff…

嵌入式硬件工程师与嵌入式软件工程师的区别(详细版)

嵌入式硬件工程师与嵌入式软件工程师的区别&#xff08;详细版&#xff09; 这里写目录标题 嵌入式硬件工程师与嵌入式软件工程师的区别&#xff08;详细版&#xff09;什么是嵌入式硬件工程师&#xff1f;什么是嵌入式软件工程师&#xff1f;嵌入式硬件工程师与嵌入式软件工程…

css 下拉框展示:当hover的时候展示下拉框 z-index的用法解释

代码如下&#xff1a; <template><div class"outer"><div class"left"></div><div class"aTest2"><div class"box">显示方框</div><div class"aTest3"></div></…

【SQL】指定时间段的下单产品

目录 语法 需求 示例 分析 代码 语法 SUM(column_name) SUM 是一个聚合函数&#xff08;Aggregate Function&#xff09;&#xff0c;用于计算数字列中值的总和。当你需要对表中的某一列数值进行求和时&#xff0c;SUM 函数就显得非常有用。它通常与 GROUP BY 语句一起使用…

运算符两边的数据类型

6-3 类型转换 1.非赋值运算的类型转换 &#xff08;1&#xff09;水平方向的转换&#xff1a;所有的char型和short型自动地转换成int 型&#xff0c;所有的unsigned short 型自动地转换成unsigned型&#xff0c;所有的long型自动地转换成unsigned long 型&#xff0c;所有的f…

exBase

1.准备工作 1.端口配置 下列为默认端口号&#xff0c;若部分端口号已被占用&#xff0c;用户可以根据实际情况进行修改。 端口号 说明 31030 exBase默认端口 31003 配置库默认端口 2181 zookeeper默认端口 9092 kafka默认端口 8091 metaNode的RPC端口 8092 node…

毕业论文写作全攻略,让你轻松过关!

姐妹们&#xff0c;毕业论文是大学旅程的最后一站&#xff0c;也是展示我们学术成果的重要时刻。但是&#xff0c;毕业论文该怎么写呢&#xff1f;别担心&#xff0c;我来告诉你&#xff01;&#x1f4da; writehelp智能写作辅导&#xff1a;http://www.writehelp.vip/?sid17…

线性基学习DAY2

今天是第二题学习线性基&#xff0c;让我对线性基的认识更多了&#xff0c;线性基其实就是去处理整个区间异或最值问题的 我们来看一下昨天的一道题 P4570 [BJWC2011] 元素 昨天其实这题我尝试了两次&#xff0c;一种是普通消元去求解&#xff0c;另一种是高斯消元去求解&…

异地如何进行跨地区协作传输文件?

跨区域协作现在是很多企业的常态了&#xff0c;无论是跨国公司还是国内多地区运营的企业&#xff0c;高效、可靠的文件传输协作都是业务顺利进行的关键。然而&#xff0c;异地传输文件常常面临诸多挑战&#xff0c;如何选择合适的工具和服务成为企业必须考虑的问题。 异地传输文…

【ADC】ΔΣ ADC 中数字滤波器的延迟以及 SAR ADC 与 ΔΣ ADC 的差异对比总结

本文学习于TI 高精度实验室课程&#xff0c;深入探讨 delta-sigma 转换器中使用的数字滤波器。具体来说&#xff0c;本文将重点介绍数字滤波器如何引入延迟&#xff0c;因为这是 SAR 和 delta-sigma ADC 之间的显著差异。 文章目录 一、低延迟数字滤波器二、高延迟数字滤波器三…

妙手上线TikTok Shop组包预报功能,助力全球跨境店卖家大促快速发货!

众所周知&#xff0c;每年的Q4可以说是所有东南亚跨境卖家的旺季&#xff0c;10月起&#xff0c;各种促销活动如10.10品牌大促和双十一大促等接踵而至&#xff0c;为卖家们带来了新的增长机遇。 特别是TikTok Shop这个新兴平台&#xff0c;更是充满无限潜力&#xff0c;根据数…

AIGAME的核心技术竞争力与未来生态规划

AIGAME凭借其领先的区块链和人工智能技术&#xff0c;打造了全球首个融合链游、DeFi和加密聊天的Web3娱乐平台。平台的核心技术创新和多元化生态规划&#xff0c;将推动全球虚拟资产管理和娱乐行业的变革。 AIGAME的核心技术竞争力源于其对区块链和人工智能&#xff08;AI&…

基于nodejs+vue的农产品销售管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

队列的基本概念以及模拟使用

1.队列的概念&#xff1a; 只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的线性表&#xff0c;队列具有先进先出FIFO 入队列 :进行插入操作的一端称为队尾. 出队列:进行删除操作的一端称为队头。 图例如下&#xff1a; 2.Queue是一个接口&…