Java多线程二-线程安全
1、线程安全问题
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
2、实例:取钱的线程安全问题
2.1、场景
小明和小红是夫妻,他们有个共同账户,余额是十万元,如果两人同时取钱并且各自取十万元,可能会出现什么问题?
2.2、模拟取钱案例
分析:
1、需要提供一个账户类,接着创建一个账户对象代表2个人的共享账户。
2、需要定义一个线程类(用于创建两个线程,分别代表小明和小红)。
3、创建2个线程,传入同一个账户对象给2个线程处理。
4、启动两个线程,同时去同一个账户对象中取钱10万元。
public class ThreadTest {public static void main(String[] args) {//1、创建一个账户对象,代表两个人的共享账户Account acc = new Account("ICBC-110",1000000);//2、创建两个线程,分别代表小明、小红,再去同一个账户下取出10万元。new DrawThread(acc,"小明").start();//小明new DrawThread(acc,"小红").start();//小红}
}public class Account {private String cargId;//卡号private double money;//余额public Account() {}public Account(String cargId, double money) {this.cargId = cargId;this.money = money;}public void drawMoney(double i){//判断谁来取钱String name = Thread.currentThread().getName();//判断余额是否足够if (this.money>=i){System.out.println(name+"来取钱:"+i+"成功");this.money -= i;System.out.println(name+"来取钱后,"+"余额剩余:"+this.money);}else{System.out.println(name+"来取钱:余额不足");}}public String getCargId() {return cargId;}public void setCargId(String cargId) {this.cargId = cargId;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}
}public class DrawThread extends Thread{private Account account;public DrawThread(Account a,String name) {super(name);this.account = a;}@Overridepublic void run() {//super.run();//取钱account.drawMoney(1000000);}
}//执行结果
小红来取钱:1000000.0成功
小明来取钱:1000000.0成功
小红来取钱后,余额剩余:0.0
小明来取钱后,余额剩余:-1000000.0
2.3、解决办法:线程同步
2.3.1、线程同步的思想
小明和小红都使用了取钱线程,两个线程同时执行了,就出现了都取成功,余额出现了负数。
2.3.2、线程同步的常见方案
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
加锁的三种方式:1、同步代码块;2、同步方法;3、Lock锁
2.3.3、同步代码块
作用:把访问共享资源的核心代码给上锁,以此保证线程安全。
synchronized(同步锁){
访问共享资源的核心代码
}
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行,执行过程还是加锁再解锁
ps:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出现bug。
//将主业务设置为同步代码块synchronized ("同步锁") {if (this.money>=i){System.out.println(name+"来取钱:"+i+"成功");this.money -= i;System.out.println(name+"来取钱后,"+"余额剩余:"+this.money);}else{System.out.println(name+"来取钱:余额不足");}}
//执行结果
小明来取钱:1000000.0成功
小明来取钱后,余额剩余:0.0
小红来取钱:余额不足
如果有其他人来取钱
Account acc1 = new Account("ICBC-112",1000000);
new DrawThread(acc1,"小黑").start();//小黑
new DrawThread(acc1,"小白").start();//小白
此时就会出现这情况
小白来取钱:1000000.0成功
小白来取钱后,余额剩余:0.0
小红来取钱:1000000.0成功
小红来取钱后,余额剩余:0.0
小黑来取钱:余额不足
小明来取钱:余额不足
解决办法就是将synchronized里面的参数改为this
//this代表共享资源synchronized (this) {if (this.money>=i){System.out.println(name+"来取钱:"+i+"成功");this.money -= i;System.out.println(name+"来取钱后,"+"余额剩余:"+this.money);}else{System.out.println(name+"来取钱:余额不足");}}
执行结果
小黑来取钱:1000000.0成功
小黑来取钱后,余额剩余:0.0
小明来取钱:1000000.0成功
小白来取钱:余额不足
小明来取钱后,余额剩余:0.0
小红来取钱:余额不足
扩展:静态方法
当静态方法使用同步锁的时候,可以直接使用类名来作为参数
public static void test(){synchronized (Account.class){}}
PS:使用规范:
- 建议使用共享资源做为锁对象,对于实力方法建议使用this作为锁对象。
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象。
2.3.4、同步方法
作用:把访问共享资源的核心方法给上锁,以此来保证线程安全
修饰符 synchronized 返回值类型 方法名称(形参列表){
操作共享资源的代码
}
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
//同步方法,内部包含thispublic synchronized void drawMoney(double i){//判断谁来取钱String name = Thread.currentThread().getName();//判断余额是否足够if (this.money>=i){System.out.println(name+"来取钱:"+i+"成功");this.money -= i;System.out.println(name+"来取钱后,"+"余额剩余:"+this.money);}else{System.out.println(name+"来取钱:余额不足");}}
//执行结果
小红来取钱:1000000.0成功
小红来取钱后,余额剩余:0.0
小明来取钱:余额不足
同步方法底层原理:
- 同步方法其实底层也是有隐士锁对象的,只是锁的范围是整个方法代码。
- 如果方法是实例方法:同步方法默认用this作为的锁对象。
- 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
2.3.5、同步代码块与同步方法比较
- 范围上:同步代码块锁的范围更小,同步方法锁的范围更大,锁的范围越小,性能越好。
- 可读性:同步方法更好
2.3.6、Lock锁
- 是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
- 是接口,不能直接实例化,可以采用他的实现类ReentrantLock来构建Lock锁对象。
//创建一个锁对象
private Lock lock = new ReentrantLock();//加锁lock.lock();//判断余额是否足够if (this.money>=i){System.out.println(name+"来取钱:"+i+"成功");this.money -= i;System.out.println(name+"来取钱后,"+"余额剩余:"+this.money);}else{System.out.println(name+"来取钱:余额不足");}//解锁lock.unlock();
//执行结果
小明来取钱:1000000.0成功
小明来取钱后,余额剩余:0.0
小红来取钱:余额不足
ps:注意
- 创建的时候增加final,保证不可修改:private final Lock lock = new ReentrantLock();
- 解锁最好放在finally中:
//加锁lock.lock();//判断余额是否足够try {if (this.money>=i){System.out.println(name+"来取钱:"+i+"成功");this.money -= i;System.out.println(name+"来取钱后,"+"余额剩余:"+this.money);}else{System.out.println(name+"来取钱:余额不足");}} catch (Exception e) {throw new RuntimeException(e);} finally {//解锁lock.unlock();}
这样的好处就是,如果出现错误,还能继续执行
构造器 | 说明 |
public ReentrantLock() | 获得Lock锁的实现类对象 |
常用方法 | |
方法名称 | 说明 |
void lock() | 获得锁 |
void unlocak() | 释放锁 |
2.4、线程通信
当多个线程共同操作共享资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。
2.4.1、常见模型
- 生产者负责生产数据。
- 消费者线程负责消费生产者生产的数据。
- 注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,通知生产者生产。
2.4.2、 案例
Object类的等待和唤醒方法:
方法名称 | 说明 |
void wait() | 让当前线程等待并释放所占锁,直到令一个线程调用notify()方法或notifyAll()方法 |
void notify() | 唤醒正在等待的单个线程 |
void notifyAll() | 唤醒正在等待的所有线程 |
注意:上述方法应该使用当前同步锁对象进行调用
public class ThreadTest {public static void main(String[] args) {Desk desk = new Desk();//创建三个生产者线程new Thread(() ->{desk.put();},"生产者1").start();new Thread(() ->{desk.put();},"生产者2").start();new Thread(() ->{desk.put();},"生产者3").start();//创建两个消费者线程new Thread(() ->{while (true) {desk.get();}},"消费者1").start();new Thread(() ->{while (true) {desk.get();}},"消费者2").start();}
}public class Desk {private List<String> list = new ArrayList<>();//放1个包子的方法//三个生产者public synchronized void put() {String name = Thread.currentThread().getName();//判断是否有包子try {if (list.size() == 0){list.add(name + "做的肉包子");System.out.println(name + "做了一个肉包子");Thread.sleep(2000);//唤醒别人,等待自己//先唤醒,再等待this.notifyAll();this.wait();}else{//其他生产者进来,发现有包子,不做了//唤醒别人,等待自己//先唤醒,再等待this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}//取一个包子的方法//两个消费者public synchronized void get() {String name = Thread.currentThread().getName();try {if (list.size() == 1){//有包子,吃掉System.out.println(name + "吃了:"+list.get(0));//清空listlist.clear();Thread.sleep(1000);//唤醒别人,等待自己//先唤醒,再等待this.notifyAll();this.wait();}else{//没有包子//唤醒别人,等待自己//先唤醒,再等待this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}
}
执行结果
生产者1做了一个肉包子
消费者2吃了:生产者1做的肉包子
生产者3做了一个肉包子
消费者1吃了:生产者3做的肉包子
相关文章:

Java多线程二-线程安全
1、线程安全问题 多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。 2、实例:取钱的线程安全问题 2.1、场景 小明和小红是夫妻,他们有个共同账户,余额是十万元,如果两人同时取钱并且各自取…...
Rust个人学习之结构体
第一反应,Rust结构体跟python的很像,不知道感觉对不对; 书中提到第一反应,Rust结构体跟python的很像,不知道感觉对不对; 书中提到:结构体是一种自定义数据类型,它允许命名多个相关的…...
kafka详细讲解与安装
Kafka是一种分布式流处理平台,具有高吞吐量、可扩展性和容错性。它最初由LinkedIn开发,现已成为Apache软件基金会的顶级项目。Kafka广泛应用于实时数据流处理、日志收集、消息队列等场景。 以下是关于Kafka的简要讲解和安装步骤: 一、Kafka…...

在我国干独立游戏开发有多难?
游戏独立开发在中国,一直以来都是一条充满挑战的道路。尽管有着无限的激情和创意,但面对市场、资金、政策等多方面的困难,许多独立开发者在这条路上艰难前行。 首先,市场竞争激烈是中国游戏独立开发者面临的首要挑战。随着游戏产…...
不可错过的网上宝藏:2023年必看的顶级资源大盘点!
亲爱的“AI uTools”读者们,大家好! 在这个信息爆炸的时代,互联网上充满了无数的资源,但如何从这海量信息中挑选出真正有用的宝藏呢?今天,我为大家精心挑选了一系列优质网站资源,涵盖了从文本处…...

日本服务器访问速度和带宽有没有直接关系?
对于许多网站和应用程序来说,服务器的访问速度是至关重要的。用户希望能够快速加载页面、上传和下载文件,而这些都与服务器的带宽有关。那么,日本服务器的访问速度和带宽之间是否存在直接关系呢? 我们需要了解什么是带宽。带宽是指网络…...
[vxe-table] vxe-table-column配合v-if导致列样式与位置错乱
<vxe-table-column v-if"pageInfo.id 4 ||pageInfo.id 8" title"上报类型" width"100" key1><template v-slot"{row}"><span>咨询工具</span></template> </vxe-table-column>//或者<vxe-ta…...
C# 忽略大小写
在 C# 中,你可以通过以下几种方式来忽略大小写: 使用 ToLower 或 ToUpper 方法将字符串转换为全小写或全大写,然后进行比较。使用 Compare 或 CompareOrdinal 方法,并传入正确的 StringComparer 实例以指示比较应该忽略大小写。使…...

大数据技术之数据安全与网络安全——CMS靶场(文章管理系统)实训
大数据技术之数据安全与网络安全——CMS靶场(文章管理系统)实训 在当今数字化时代,大数据技术的迅猛发展带来了前所未有的数据增长,同时也催生了对数据安全和网络安全的更为迫切的需求。本篇博客将聚焦于大数据技术背景下的数据安全与网络安全ÿ…...
[datastore@cyberfear.com].Elbie、[thekeyishere@cock.li].Elbie勒索病毒数据怎么处理|数据解密恢复
引言: 随着科技的进步,勒索病毒变得越来越复杂,而[datastorecyberfear.com].Elbie、[thekeyisherecock.li].Elbie勒索病毒是其中的一种令人头疼的威胁。本文将深入介绍[datastorecyberfear.com].Elbie、[thekeyisherecock.li].Elbie勒索病毒…...

【咕咕送书 | 第六期】深入浅出阐述嵌入式虚拟机原理,实现“小而能”嵌入式虚拟机!
🎬 鸽芷咕:个人主页 🔥 个人专栏:《粉丝福利》 《linux深造日志》 ⛺️生活的理想,就是为了理想的生活! 文章目录 ⛳️ 写在前面参与规则引言一、为什么嵌入式系统需要虚拟化技术?1.1 专家推荐 二、本书适合谁&#x…...

论文阅读——DDeP(cvpr2023)
分割标签耗时且贵,所以常常使用预训练提高分割模型标签有效性,反正就是,需要一个预训练分割模型。典型的分割模型encoder部分通过分类任务预训练,decoder部分参数随机初始化。作者认为这个方法次优,尤其标签比较少的情…...

Docker | Docker常用命令
Docker | Docker常用命令 ✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏:Docker系列 ✨…...
精进:简单聊聊华为战略与DSTE
首发:麦子禾 作者:石头 在以前专题文中,石头聊华为市场管理MM、基于价值驱动的业务设计VDBD、轻量级战略流程五看三定、业务领先模型BLM、业务执行力模型BEM比较多,印象中石头没有具体谈过DSTE(从战略规划到执行&…...

强制删除文件
DEL /F /A /Q \\?\%1 RD /S /Q \\?\%1 强制删除文件,新建一个文本文件,将以上代码复制到文档中,保存,将文档重命名为delete.bat 如果弹窗提示修改后缀名可能导致文件不可用,也点击确认修改文件名称. 将需要强制删除的文件拖拽到这个delete.bat文件上,显示使用delete.bat打…...

Vue+ElementUI+C#技巧分享:周数选择器
文章目录 前言一、周数的计算逻辑1.1 周数的定义1.2 年初周数的确定1.3 周数的计算方法 二、VueElementUI代码实现2.1 计算周数2.2 获取周的日期范围2.3 根据周数获取日期范围2.4 控件引用2.4.1 控件引用代码分析2.4.2 初始化变量代码分析 2.5 周数选择器完整代码 三、C#后端代…...

【算法】FFT-1(递归实现)(不包括IFFT)
FFT 多项式多项式乘法复数及运算导数泰勒公式及展开式欧拉公式单位根 FFTCode IFFT 多项式 我们从课本中可以知道,一个 n − 1 n-1 n−1 次的多项式可以写成 a 0 a 1 x a 2 x 2 a 3 x 3 ⋯ a n − 1 x n − 1 a_{0}a_{1}xa_{2}x^2a_{3}x^3\dotsa_{n-1}x^{n-…...
大模型训练效率提升至2.6倍,腾讯Angel机器学习框架升级
在算力紧缺的背景下,如何提升大模型训练和推理的效率,并降低成本,成为业界关注的焦点。 11月23日,腾讯披露,腾讯混元大模型背后的自研机器学习框架Angel再次升级,大模型训练效率提升至主流开源框架的2.6倍…...

【方块消消乐】方块消除游戏-微信小程序开发流程详解
有做过俄罗斯方块游戏小程序的经验,这次有做了一个消灭方块的游戏,实现过程很顺利,游戏看着和之前做的俄罗斯方块游戏很像,这里调整了玩法,试玩感觉还可以,接下来给大家讲一讲消灭方块游戏开发过程。 俄罗斯…...
mybatis配置文件中配置类型别名的方式
在MyBatis配置文件(通常是mybatis-config.xml)中,可以通过以下方式配置类型别名: 1. 使用typeAliases元素配置全局类型别名 <configuration> <typeAliases> <typeAlias alias"YourAlias" type"…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...

基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
区块链技术概述
区块链技术是一种去中心化、分布式账本技术,通过密码学、共识机制和智能合约等核心组件,实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点:数据存储在网络中的多个节点(计算机),而非…...
二维FDTD算法仿真
二维FDTD算法仿真,并带完全匹配层,输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...