【老白学 Java】线程的并发问题(一)

embedded/2025/1/17 15:17:07/

线程的并发问题(一)

码老白
文章来源:《Head First Java》修炼感悟。

尽管多线程设计很令人兴奋,但还是要尽可能「避坑」。 想象一下,当你用精心设计的两个线程同时维护某个对象数据时,这个数据安全吗? 这就是本文要讨论的问题 - 线程的并发性可能会破坏数据安全。

一、情侣的公共账户

现实生活当中可能会发生这样的事情:热恋中的花花和草草同时入职名企,兴奋之余对未来做起了规划。 两人约定每月工资都存进同一个账户,不限制花销,但绝对不允许透支。 可以保证的是,二人都非常守信。但这样真的可以吗? 如果两人事先没有沟通,这个账户还是可能会出现透支情况。

我们用代码演绎一下这个情节:

java">// 账户操作类
public class JobRunnable implements Runnable {// 新建一个公共账户private BankAcount acount = new BankAcount();public void run() {// 取钱操作,每次取出 10 元钱// 如果账户余额小于零,即表示账户透支for(int i = 0; i < 10; i++) {withdrawal(10);			if (acount.getBalance() < 0) {System.out.println("Overdrawn!");}}}// 创建两个线程,同时提取账户余额public static void main(String[] args) {JobRunnable job = new JobRunnable();Thread huahua = new Thread(job);Thread caocao = new Thread(job);huahua.setName("HuaHua");caocao.setName("CaoCao");huahua.start();caocao.start();}// 从账户中提取指定金额。// 正常情况下,余额不足时看起来绝对不会执行的 acount.withdrawal()// 方法也被执行了,这是因为其中一个线程查询了余额后,同时另一个线程// 做了提取操作,线程间并没有得到更新后的余额,所以导致对账户余额判断// 出现错误,这就是典型的线程并发性导致的问题。private void withdrawal(int amount) {// 账户余额不足,直接返回if (acount.getBalance() < amount) {System.out.println("Sorry, not enough for " + Thread.currentThread().getName());return;}// 准备取钱System.out.println(Thread.currentThread().getName() + " is about to withdraw.");try {System.out.println(Thread.currentThread().getName() + " is going to sleep.");Thread.sleep(500);	// 让线程小睡一会儿} catch (InterruptedException e) {e.printStackTrace();}// 线程被唤醒,开始取钱System.out.println(Thread.currentThread().getName() + " woke up.");acount.withdrawal(amount);// 交易结束System.out.println(Thread.currentThread().getName() + " completes the withdrawal.");}
}
// 账户类
class BankAcount {private int balance = 100;	// 账户余额public int getBalance() {return balance;}// 取出指定的钱数并更新余额public void withdrawal(int amount) {balance -= amount;}
}

运行这段代码:
账户透支

不出意外,这个账户在不经意间透支了,下个月可咋过? 现在我们来梳理一下为什么会出现这个问题:

  • 花花准备从账户中取钱,查询后发现账户余额足够,就放心地睡觉去了;
  • 草草也想从账户中取钱,也查询了账户余额足够,也睡了一会儿;
  • 花花醒来后直接从账户中取走了所需的钱;
  • 很快草草也睡醒了,他记得余额足够所以直接取走了他所需的钱;
  • 结果,两人在都不知情的情况下,账户透支了。

其实两人想法是好的,但缺少了保护机制:在账户取钱的过程中,不应该允许其他人在同一时时操作该账户,除非前面的交易已经完成。

二、为账户加上同步锁

我们再来分析一下应该怎样保护好这个账户:

  • 为账户设置一道锁和一把钥匙,平时锁处于开放状态谁都可以操作;
  • 当操作账户时,锁住账户并收好钥匙,确保除了自己谁也无法操作该账户;
  • 账户操作结束后,打开账户锁并归还钥匙,确保下次可以正常操作。

我们只要锁住账户存取的方法就可以,请看代码:

java">// 从账户中取钱,这次有 synchronized 锁
private synchronized void withdrawal(int amount) {if (acount.getBalance() < amount) {System.out.println("Sorry, not enough for " + Thread.currentThread().getName());return;}// 其它代码...
}

注意,withdrawal() 方法使用了 synchronized 关键字修饰,表示同步的意思,它的作用就是保证同一时间只能有一个线程执行该方法。 我们来看看加上了同步锁后的效果:
同步锁
可以了,这次账户不会透支了!


《 上一篇 线程的调度机制

http://www.ppmy.cn/embedded/154687.html

相关文章

QT开发技术 【基于TinyXml2的对类进行序列化和反序列化】一

一、对TinyXml2 进行封装 使用宏 实现序列化和反序列化 思路&#xff1a; 利用宏增加一个类函数&#xff0c;使用序列化器调用函数进行序列化 封装宏示例 #define XML_SERIALIZER_BEGIN(ClassName) \ public: \virtual void ToXml(XMLElement* parentElem, bool bSerialize …

3、C#基于.net framework的应用开发实战编程 - 实现(三、一) - 编程手把手系列文章...

三、 实现&#xff1b; 三&#xff0e;一、实现数据库操作&#xff1b; 对于数据库的操作&#xff0c;以前都是有ODBC的接口&#xff0c;通过Helper类库进行的操作。此文主要介绍例子里对数据库操作的实现。 1、 SQLiteHelper&#xff1b; SQLite主要是用C编写的&#xff0c;但…

【概率论与数理统计】第三章 多维随机变量及其分布(2)

定义7&#xff1a;若二维连续型随机变量 ( X , Y ) (X,Y) (X,Y)的概率密度为&#xff1a; f ( x , y ) 1 2 π σ 1 σ 2 1 − ρ 2 e − 1 2 ( 1 − ρ 2 ) [ ( x − μ 1 ) 2 σ 1 2 − 2 ρ ( x − μ 1 ) ( y − μ 2 ) σ 1 σ 2 ( y − μ 2 ) 2 σ 2 2 ] f(x,y) \fra…

(经过验证)在 Ubuntu 系统中为 VSCode、PyCharm 终端及 Jupyter Notebook 配置代理的完整方案

文章目录 1. 通过系统环境变量配置代理步骤一&#xff1a;打开终端步骤二&#xff1a;编辑 ~/.bashrc 文件步骤三&#xff1a;添加代理环境变量步骤四&#xff1a;保存并关闭文件步骤五&#xff1a;使配置生效步骤六&#xff1a;重启相关应用步骤七&#xff1a;使用代理函数 2.…

接口传参 data格式和json格式区别是什么

接口传参 data格式和json格式区别是什么 以下是接口传参 data 格式和 JSON 格式的区别&#xff1a; 定义和范围 Data 格式&#xff1a; 是一个较为宽泛的概念&#xff0c;它可以指代接口传递参数时所使用的任何数据的组织形式。包括但不限于 JSON、XML、Form 数据、纯文本、二进…

微信小程序在使用页面栈保存页面信息时,如何避免数据丢失?

微信小程序在使用页面栈保存页面信息时避免数据丢失的方法&#xff1a; 一、使用全局变量存储关键数据&#xff1a; 定义一个全局变量&#xff0c;例如在 app.js 中&#xff0c;用于存储页面的重要信息。在页面的 onHide 或 onUnload 生命周期中&#xff0c;将需要保存的数据…

【江西新能源科技职业学院主办 | JPCS(ISSN: 1742-6588)出版,快速见刊检索】2025年可再生能源与节能国际会议(REEC 2025)

2025年可再生能源与节能国际会议&#xff08;REEC 2025&#xff09; 2025 International Conference on Renewable Energy and Energy Conservation 2025年3月7-9日 中国江西新余 大会官网&#xff1a;www.icreec.org【参会投稿】 报名/截稿&#xff1a;见官网 提交收录&…

在eNSp上telnet一下吧

在上篇博客&#xff1a;DNS 我们提到了telnet和设备带外管理、带内管理&#xff0c;它确实是非常有趣的一个知识点哦&#xff0c;接下来我们一起来学习学习吧~ Telnet&#xff08;远程登陆协议&#xff09; Telnet基于TCP 23号端口&#xff0c;典型的C/S架构模式&#xff0c;是…