【老白学 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 大洋,难道是谁私藏了小金库?
罪魁祸首还是线程并发
草草突然灵光一闪,难道还是线程的并发性惹出的麻烦? 的确,「罪魁祸首」还是线程的并发。 再来梳理一下上面转账的过程:
- 首先花花进入转账方法
increment()
,睡了 1 毫秒,还没有更新余额; - 草草也进入转账方法,获取了尚未更新的余额,然后也睡了 1 毫秒;
- 花花睡醒后开始更新余额;
- 草草也在睡醒后更新了余额,但不幸的是,他在睡前获取的是没有更新过的余额,所以草草使用了旧的余额覆盖掉了花花刚刚更新过的余额。 这样一来,这一轮账户就少了 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;
}
看看它们的运行结果:
三次运行结果都得到了正确的余额,花花和草草终于可以睡个好觉了。
《 上一篇 线程的并发问题(一) | 下一篇 线程死锁是怎么回事 》 |
---|