多线程---线程同步,线程通信

线程同步

1.概述

线程同步是多线程编程中的一个重要概念,它指的是在多线程环境中,通过一定的机制保证多个线程按照某种特定的方式正确、有序地执行。这主要是为了避免并发问题,如死锁、竞态条件、资源争用等,确保数据的一致性和完整性。

当多个线程共享同一份资源时,由于线程的执行顺序是不确定的,可能会出现线程安全问题。例如,两个线程同时对一个共享变量进行操作,可能会出现预期之外的结果。

如下:

小明和小弘对同一账号取钱,会出现余额为负的情况

package Synchronization;
//操作账户
public class Account {private String cardId;private Double amount;public Account(String cardId, Double amount) {this.cardId = cardId;this.amount = amount;}public void withDrawMoney(Double amount){if(this.amount>=amount){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}this.amount=this.amount-amount;System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);}else {System.out.println(Thread.currentThread().getName()+"来取钱失败!");}}public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public Double getAmount() {return amount;}public void setAmount(Double amount) {this.amount = amount;}
}

package Synchronization;public class Main {public static void main(String[] args) {Account account = new Account("w2xId", (double) 1000);//初始化账户//实例化小明取钱线程new Thread(new Runnable() {@Overridepublic void run() {account.withDrawMoney((double) 1000);}},"小明").start();//实例化小弘取钱线程new Thread(new Runnable() {@Overridepublic void run() {account.withDrawMoney((double) 1000);}},"小弘").start();}
}

结果:

为了避免这种情况,就需要对线程进行同步,即保证同一时刻只有一个线程可以对共享资源进行操作。

2.线程同步的三种方式

1.同步代码块

在Java中,同步代码块是一种确保线程同步的机制,它允许你指定一段代码只能由一个线程在任何给定时间执行。同步代码块是通过在代码块前加上synchronized关键字和一个锁对象来实现的。这个锁对象可以是任何对象,当线程尝试进入同步代码块时,它必须首先获得这个锁。

关键修改部分

//同步代码块
synchronized (this) {if(this.amount>=amount){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}this.amount=this.amount-amount;System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);}else {System.out.println(Thread.currentThread().getName()+"来取钱失败!");}
}

这里取this作为锁对象,this指代Account对象,也就是Acount对象为锁对象,且此程序只有一个Account实例化对象。

执行结果

2.同步方法

在Java中,同步方法也是一种实现线程同步的常用方式。通过在方法声明前加上synchronized关键字,可以确保该方法在任何时刻只被一个线程访问。同步方法会隐式地锁定当前实例(this),即如果两个线程同时访问同一个对象的同一个同步方法,那么只有一个线程能够执行该方法,另一个线程必须等待。

关键代码修改如下:

synchronized public void withDrawMoney(Double amount){//同步代码块if(this.amount>=amount){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}this.amount=this.amount-amount;System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);}else {System.out.println(Thread.currentThread().getName()+"来取钱失败!");}}

3.Lock锁

在Java中,除了使用内置的synchronized关键字实现同步外,还可以使用java.util.concurrent.locks包中提供的Lock接口及其实现类(如ReentrantLock)来实现更灵活和强大的线程同步。Lock接口提供了一种机制,可以显式地获取和释放锁,而不是像synchronized那样隐式地获取和释放锁。

使用Lock接口的主要优势包括:

  1. 灵活性:可以中断正在等待锁的线程,可以尝试获取锁而不必阻塞,以及可以释放锁,即使锁是由其他线程获取的。
  2. 可响应性:相比于synchronized,Lock通常具有更好的响应性,因为它允许更细粒度的锁控制。
  3. 条件支持:Lock接口与Condition接口一起使用,可以实现更复杂的线程同步需求。

Lock锁实现步骤:

1.创建Lock锁

2.加锁

3.解锁

关键代码修改如下:

package Synchronization;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Account {private String cardId;private Double amount;private final Lock lock=new ReentrantLock();//1.创建锁对象public Account(String cardId, Double amount) {this.cardId = cardId;this.amount = amount;}
//    synchronized public void withDrawMoney(Double amount){
//        //同步代码块
//            if(this.amount>=amount){
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                this.amount=this.amount-amount;
//                System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);
//            }else {
//                System.out.println(Thread.currentThread().getName()+"来取钱失败!");
//            }
//
//    }
synchronized public void withDrawMoney(Double amount){lock.lock();//2.加锁if(this.amount>=amount){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}this.amount=this.amount-amount;System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);}else {System.out.println(Thread.currentThread().getName()+"来取钱失败!");}lock.unlock();//3.解锁}//    public void withDrawMoney(Double amount){
//        //同步代码块
//        synchronized (this) {
//            if(this.amount>=amount){
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                this.amount=this.amount-amount;
//                System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);
//            }else {
//                System.out.println(Thread.currentThread().getName()+"来取钱失败!");
//            }
//        }
//
//    }public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public Double getAmount() {return amount;}public void setAmount(Double amount) {this.amount = amount;}
}

线程通信

1.概述

生产者消费者模型在线程通信中是一个经典的应用场景。这个模型主要用来解决生产者和消费者之间的同步问题,确保两者之间的顺畅协作。在这个模型中,生产者负责生成数据并将其放入共享缓冲区,而消费者则从缓冲区中取出数据进行处理。

生产者消费者模型的关键点:

  1. 共享缓冲区:通常是一个队列或其他数据结构,用作生产者和消费者之间的通信媒介。生产者将生产的数据放入缓冲区,而消费者从缓冲区中取出数据进行处理。
  2. 同步机制:由于生产者和消费者可能同时访问缓冲区,因此需要一种同步机制来确保数据的一致性和避免竞态条件。这通常通过锁、信号量或其他同步原语来实现。在Java中,可以使用synchronized关键字、Lock接口或BlockingQueue来实现同步。
  3. 生产者和消费者的协作:当缓冲区满时,生产者需要等待缓冲区有空闲空间才能继续生产数据。同样,当缓冲区为空时,消费者需要等待缓冲区中有数据才能继续消费。这种协作通过线程间的通信来实现,生产者通知消费者缓冲区有新数据,消费者通知生产者缓冲区有空闲空间。

2.实例

三个生产者生产包子,两个消费者吃包子,每次生产者将一个包子放到桌子上并通知消费者,消费者拿取包子后通知生产者生产包子。

package Synchronization;import java.util.ArrayList;
import java.util.List;/*** 如果桌子上没有包子,则拿包子线程等待,唤醒其他所有线程* 如果桌子上有包子,则放包子线程等待,唤醒其他所有线程*/
public class Desk {private List<String> list=new ArrayList<>();//放包子,通过同步代码块,保证生产者只有一个在生产包子public synchronized void put(){String name = Thread.currentThread().getName();if(list.size()==0){list.add("生产了一个包子");System.out.println(name+list.get(0));try {Thread.sleep(2000);this.notifyAll();//唤醒所有的线程this.wait();//等待} catch (Exception e) {e.printStackTrace();}}else {this.notifyAll();//唤醒所有的线程try {this.wait();//等待} catch (InterruptedException e) {e.printStackTrace();}}}//拿包子,通过同步代码块,保证只有一个消费者拿取包子public synchronized void get(){String name = Thread.currentThread().getName();if(list.size()==1){list.clear();System.out.println(name+"拿了一个包子");try {this.notifyAll();//唤醒所有的线程this.wait();//等待} catch (Exception e) {e.printStackTrace();}}else {this.notifyAll();//唤醒所有的线程try {this.wait();//等待} catch (InterruptedException e) {e.printStackTrace();}}}
}
package Synchronization;public class CommunicationModel {public static void main(String[] args) {Desk desk=new Desk();//创建三个生产者线程new Thread(new Runnable() {@Overridepublic void run() {while (true){desk.put();}}},"厨师1").start();new Thread(new Runnable() {@Overridepublic void run() {while (true){desk.put();}}},"厨师2").start();new Thread(new Runnable() {@Overridepublic void run() {while (true){desk.put();}}},"厨师3").start();//创建两个消费者线程new Thread(new Runnable() {@Overridepublic void run() {while (true){desk.get();}}},"客人1").start();new Thread(new Runnable() {@Overridepublic void run() {while (true){desk.get();}}},"客人2").start();}
}

3.结果

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

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

相关文章

Leetcode-1523. 在区间范围内统计奇数数目

题目&#xff1a; 给你两个非负整数 low 和 high 。请你返回 low 和 high 之间&#xff08;包括二者&#xff09;奇数的数目。 示例 1&#xff1a; 输入&#xff1a;low 3, high 7 输出&#xff1a;3 解释&#xff1a;3 到 7 之间奇数数字为 [3,5,7] 。 示例 2&#xff1a; 输…

中国电子学会2023年09月真题C语言软件编程等级考试一级(含解析答案)

中国电子学会考评中心历届真题&#xff08;含解析答案&#xff09; C语言软件编程等级考试一级 2023年09月 编程题五道 总分:100分一、日期输出&#xff08;20分&#xff09; 给定两个整数&#xff0c;表示一个日期的月和日。请按照“MM-DD”的格式输出日期&#xff0c…

【Java基础面试题】

目录 前言 1.1 为什么Java代码可以实现一次编写、到处运行&#xff1f; 1.2 一个Java文件里可以有多个类吗&#xff08;不含内部类&#xff09;&#xff1f; 1.3 说一说你对Java访问权限的了解 1.4 介绍一下Java的数据类型 1.5 int类型的数据范围是多少&#xff1f; 1.6…

C#入门及进阶|数组和集合(十):Queue类

在某种线性表中&#xff0c;需要加入的元素总是插入到线性表的末端&#xff0c;且总是从线性表的头部取出或删除元素&#xff0c;我们把这种线性表称为队列。 在C#中&#xff0c;通过Queue集合来封装对队列的操作&#xff0c;在队列中对元素的操作遵循“先进先出”的原则。Queu…

【算法】问题描述关键提取——提炼一般的解决思路

文章目录 前言排序关键/关键词2389. 和有限的最长子序列 栈关键/关键词2390. 从字符串中移除星号 拓扑排序关键/关键词207. 课程表2392. 给定条件下构造矩阵 线性DP关键/关键词最长公共子序列1143. 最长公共子序列 最长递增子序列300. 最长递增子序列与最长公共子序列的联系动态…

【复现】某公司指挥调度管理平台 RCE漏洞_51

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 该平台提供强大的指挥调度功能&#xff0c;可以实时监控和管理通信网络设备、维护人员和工作任务等。用户可以通过该平台发送指令…

P1441 背包九讲(3):完全背包问题

P1441 背包九讲3&#xff1a;完全背包问题 一、原题呈现1、题目描述2、输入描述3、输出描述4、样例输入5、样例输出6、提示信息 二、思路分析1、其实这题就是01背包的变形2、但是本题全开二维数组会超内存&#xff0c;因此我们使用两个一维数组进行计算并且复制 三、整体代码 一…

视频生成模型作为世界模拟器

我们探索了在视频数据上大规模训练生成模型。具体来说&#xff0c;我们联合训练文本条件扩散模型&#xff0c;处理不同持续时间、分辨率和宽高比的视频和图像。我们利用一种在时空补丁上操作视频和图像潜码的transformer架构。我们最大的模型&#xff0c;Sora&#xff0c;能够生…

树状菜单(利用映射-bootstrap+jQuery实现折叠功能)

效果&#xff08;默认全部展开&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><…

CSS的background 背景图片自动适应元素大小,实现img的默认效果 background-size:100% 100%;

CSS的background 背景图片自动适应元素大小,实现img的默认效果 background-size:100% 100%; 关键是background-size:100% 100%; background-size:100% 100%; background-size:100% 100%; background-size:contain; 保持纵横比, 容器部分可能空白background-size:cover; 保…

解锁Spring Boot中的设计模式—02.解释器模式:探索【解释器模式】的奥秘与应用实践!

解释器模式 1.简介 解释器模式&#xff08;Interpreter Pattern&#xff09;是一种行为设计模式&#xff0c;它用于定义语言的文法&#xff0c;并且解释语言中的表达式。在Java中&#xff0c;解释器模式可以用于构建解释器以解析特定的语言或表达式&#xff0c;如数学表达式、…

C++面试宝典第28题:寻找丢失的数字

题目 给定一个包含n个整数的数组nums,其中nums[i]在区间[1, n]内。请找出所有在[1, n]范围内,但没有出现在nums中的数字,并以数组的形式返回结果。 示例1: 输入:nums = [4, 3, 2, 7, 8, 2, 3, 1] 输出:[5, 6] 示例2: 输入:nums = [1, 1] 输出:[2] 解析 初看这道题,…

【lesson57】信号量和生产者消费者模型(环形队列版)

文章目录 信号量概念信号量接口初始化销毁等待发布 基于环形队列的生产者消费者模型编码Common.hLockGuard.hppTask.hppsem.hppRingQueue.hppConProd.cc 信号量概念 POSIX信号量和SystemV信号量作用相同&#xff0c;都是用于同步操作&#xff0c;达到无冲突的访问共享资源目的…

【测试】JUnit

目 录 一.注解二.断言三.用例的执行顺序四.参数化五.测试套件 自动化就是 selenium 脚本来实现的 junit 是 java 的单亓测试工具&#xff0c;只不过我们在实现自动化的时候需要借用一下下 junit 库里面提供的一些方法 引入依赖 Junit 5 <!-- https://mvnrepository.com/a…

自然语言编程系列(二):自然语言处理(NLP)、编程语言处理(PPL)和GitHub Copilot X

编程语言处理的核心是计算机如何理解和执行预定义的人工语言&#xff08;编程语言&#xff09;&#xff0c;而自然语言处理则是研究如何使计算机理解并生成非正式、多样化的自然语言。GPT-4.0作为自然语言处理技术的最新迭代&#xff0c;其编程语言处理能力相较于前代模型有了显…

电子元器件基础5---二极管

除了电阻、电容和电感等线性元器件之外,还有二极管、三极管这些常用的非线性器件广泛应用于日常生活中。那么今天我们来介绍以下二极管这一常用的电子元器件。 一、二极管概念 二极管是用半导体材料(硅、硒、锗等)制成的一种电子器件 。二极管有两个电极,正极,又叫阳极;负…

256.【华为OD机试真题】会议室占用时间(区间合并算法-JavaPythonC++JS实现)

🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目二.解题思路三.题解代码Python题解代码JAVA题解…

django报错:Cannot use ImageField because Pillow is not installed

1、问题概述 ERRORS: accounts.User.avatar: (fields.E210) Cannot use ImageField because Pillow is not installed. HINT: Get Pillow at https://pypi.org/project/Pillow/ or run command "python -m pip install Pillow". System check identified 1 …

JDK1.8安装教程

目录 下载安装环境配置打开系统高级设置环境配置 验证安装是否成功 下载 https://www.oracle.com/java/technologies/downloads/#java8-windows 安装 打开安装包&#xff0c;点击下一步。 选择好自己熟悉的目的安装目录&#xff0c;点击下一步。 等待安装 选择好jre的安装目…

ubuntu22.04@laptop OpenCV Get Started: 013_contour_detection

ubuntu22.04laptop OpenCV Get Started: 013_contour_detection 1. 源由2. 应用Demo2.1 C应用Demo2.2 Python应用Demo 3. contour_approx应用3.1 读取图像并将其转换为灰度格式3.2 应用二进制阈值过滤算法3.3 查找对象轮廓3.4 绘制对象轮廓3.5 效果3.6 CHAIN_APPROX_SIMPLE v.s…