当前位置: 首页 > article >正文

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

线程的并发问题(二)

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

在上一篇文章中,那个公用账户莫名其妙的出现透支情况,想必大家还心有余悸。 今天再来说说线程并发性导致的另一个问题,导致公共账户的收入被无缘无故的克扣,简直是欲哭无泪。

谁私藏了小金库

多线程真是一个令人又爱又恨的存在。 花花和草草省吃俭用地过了一个月,终于熬到了开资的日子。 公司开资有个奇怪的规定,必须分 10 次转账到员工的账户,具体为什么谁也不清楚。

接下来,我们还是使用多线程来模拟转账过程:

// 转账类
public class TransferAccounts {

	public static void main(String[] args) {
		Accounts accounts = new Accounts();
		Thread huahua = new Thread(accounts);
		Thread caocao = new Thread(accounts);
		huahua.start();
		caocao.start();
	}
}
// 账户处理类
class Accounts implements Runnable {

	// 保存账户余额
	private int balance = 0;
	
	// 调用五次转账方法
	public void run() {
		for(int i = 0; i < 5; i++) {
			increment(1000);
			System.out.println("balance is " + balance);
		}
	}

	// 更新账户余额,这个方法存在线程并发问题
	public void increment(int amount) {
		int temp = balance;
		
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		balance = temp + amount;
	}
}

正常情况下,花花和草草应该收入 10000 元,因为每人转账 5 次每次 1000 元,两人总共收入 10000 元。

嗯应该没算错,确定是 10000 元。 来看看两人最终收入多少:
账户余额不对

怎么会这样! 足足少了 3000 大洋,难道是谁私藏了小金库?

罪魁祸首还是线程并发

草草突然灵光一闪,难道还是线程的并发性惹出的麻烦? 的确,「罪魁祸首」还是线程的并发。 再来梳理一下上面转账的过程:

  1. 首先花花进入转账方法 increment(),睡了 1 毫秒,还没有更新余额;
  2. 草草也进入转账方法,获取了尚未更新的余额,然后也睡了 1 毫秒;
  3. 花花睡醒后开始更新余额;
  4. 草草也在睡醒后更新了余额,但不幸的是,他在睡前获取的是没有更新过的余额,所以草草使用了旧的余额覆盖掉了花花刚刚更新过的余额。 这样一来,这一轮账户就少了 1000 元,况且这样的操作还经过了多轮。

两个线程搅在一起,账户余额被破坏也不足为奇,真搞不懂线程为什么这么喜欢睡觉。 其实也不一定是喜欢睡觉,也有可能延迟或者被其它线程插队,总之它执行到了那个位置,迫不得已地停了下来,导致其它线程乘虚而入并行操作。

使用上篇文章中介绍的同步锁方法可以解决这个问题,但还有更适合的方法,就是下面要介绍的对象锁。

使用对象锁保护账户安全

Java 中的每个对象都会有一把锁,如果对象中有 synchronized 方法,那么就必须取得这把对象锁来访问受保护的数据。 在上面的例子中,你可以这样做:

// 加上同步锁
public synchronized void increment(int amount) {
	int temp = balance;
	try {
		Thread.sleep(1); 
	} catch (InterruptedException e) {}
	balance = temp + amount;
}

也可以这样做:

// 加上对象锁,锁住需要保护的数据或者操作
public void increment(int amount) {
	synchronized(this) {
		int temp = balance;
		try {
			Thread.sleep(1); 
		} catch (InterruptedException e) {}
		balance = temp + amount;
	}
}

还可以这样做:

// 不用任何锁,把方法中的两条语句合并,
// 使其他线程没有机会插入两条代码之间进行破坏
public void increment(int amount) {
	balance += amount;
}

看看它们的运行结果:
加上了对象锁

三次运行结果都得到了正确的余额,花花和草草终于可以睡个好觉了。


《 上一篇 线程的并发问题(一)下一篇 线程死锁是怎么回事 》

http://www.kler.cn/a/508303.html

相关文章:

  • 基于 Python 的财经数据接口库:AKShare
  • win32汇编环境,窗口程序中基础列表框的应用举例
  • pytest全局配置文件pytest.ini
  • Python毕业设计选题:基于python的酒店推荐系统_django+hadoop
  • 光伏储能电解水制氢仿真模型Matlab/Simulink
  • 使用python+pytest+requests完成自动化接口测试(包括html报告的生成和日志记录以及层级的封装(包括调用Json文件))
  • JDK 8 - 新日期格式化类 DateTimeFormatter 使用
  • Spring boot框架下的RabbitMQ消息中间件
  • Spring声明式事务
  • 第22篇 基于ARM A9处理器用汇编语言实现中断<四>
  • “AI智能防控识别系统:守护安全的“智慧卫士”
  • 【进程与线程】进程的基础
  • 深度学习-88-大语言模型LLM之基于langchain的检索链
  • 【网络协议】【http】【https】AES-TLS1.2
  • 软考信安24~工控安全需求分析与安全保护工程
  • AXIOS的引入和封装
  • 对MySQL滴MVCC理解(超详细)
  • 【蓝桥杯选拔赛真题62】C++求和 第十四届蓝桥杯青少年创意编程大赛 算法思维 C++编程选拔赛真题解
  • “AI开放式目标检测系统:开启智能识别新时代
  • Linux《Linux简介与环境的搭建》
  • React 表单处理与网络请求封装详解[特殊字符][特殊字符]
  • java请编写程序,分别定义8种基本数据类型的变量,并打印变量的值。
  • 左神算法基础提升--2
  • MySQL(高级特性篇) 06 章——索引的数据结构
  • 深入浅出:Go语言中的Unicode与字符编码详解
  • C++ K2 (4)