老板让我写一个存款功能 存了100 居然蒸发了99块钱
作者 | Java圣斗士 | 原创图文,转载请注明出处
全文约2000字,阅读大约8分钟

哈喽大家好,我是又皮又可爱的Java圣斗士,关注我,每天带你飞!
前两天看了一个段子,说老板让员工写一个存款的功能,要能够并发处理,提高效能。
小伙子用100个执行绪来处理这个事情,然后用100块钱来测试,每个执行绪往账户里存1块钱,结果最后却只有1块钱余额。这是咋回事?来看看他的程式码。
首先建立一个Account账户:
public class Account {
private double balance;// 余额
public void deposit(double money) {
double newBalance = balance + money;
try {
Thread.sleep(10);// 模拟业务处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
balance = newBalance;
}
public double getBalance() {
return balance;
}
}
这个类很好理解,Account模拟一个账户,维护一个double型别的余额 balance,然后deposit()方法允许传入一个money,改变这个余额。
下面是测试类:
public class Test {
public static void main(String[] args) {
Account account = new Account();
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i = 0; i service.execute(() -> account.deposit(1));
}
service.shutdown();
while (!service.isTerminated()) { }
System.out.println("账户余额:" + account.getBalance() + "¥");
}
}
测试类首先建立了一个账户,然后通过一个for循环向能容纳100个执行绪的执行绪池service中布置任务,每个执行绪执行 account.deposit(1),即每个执行绪向账户里存一块钱,我们来看看执行结果:

天哪!为什么存了100只剩一两块钱了,我的另外那99块钱哪去了?这是要出事儿的节奏啊!

分析来分析去,原来啊,在多执行绪环境下,共同操作一个变数肯定是要有问题的。这是因为,多个执行绪之间,没有同步的情况下,非原子操作会在多执行绪下出现混乱。
小伙子想到这里,于是微微一笑,开始修改业务逻辑。
然而他并没有想到同步的问题,只是想到了原子操作,既然是原子操作,那就要把改值和赋值变成一个不可分割的操作就可以了嘛!于是他将deposit()进行了如下修改:
public void deposit(double money) {
balance += money;
try {
Thread.sleep(10);// 模拟业务处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
将balance + money改成了balance += money,小伙子想:这样紧凑的操作一定就是原子操作了,肯定没问题!执行一下看看结果:

what?!99¥?为啥还差1块钱?这不科学?
原来啊,自加运算并不是原子操作!它的执行轨迹是先加,再赋值,是一个非原子操作,那么在多个执行绪共同访问同一个变数的时候,很容易在执行过程中获得一个无效的资料,导致最后相加的结果要比逻辑小,因此,这种方法也是不行的。
所以,在处理多执行绪的时候,一定要进行同步处理,保证资料的原子操作。我们可以通过synchronized关键字来进行修改:
public synchronized void deposit(double money) {
balance += money;
try {
Thread.sleep(10);// 模拟业务处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
当然,这样一来不论是自加运算,还是加和赋值分开来,都是没问题的,因为在方法上加上synchronized则代表整个方法都需要上锁,同步执行,那么我们再来看看执行结果:

这样,执行结果就正确了。你学会了吗!?
更多多执行绪的知识,欢迎阅读下方往期精彩。
往期精彩:
《同步只会用synchronized?如果你没用过它,面试就等著挨虐吧》
《CountDownLatch——并发程式设计的别样烟火》
《执行绪池?不懂也会被问到的任务执行器》
《只知道方法区域性变数怎么行?ThreadLocal执行绪区域性变数全解析》
《再不了解一下volatile你就out了》
---欢迎关注【Java圣斗士】,我是你们的小可爱(✪ω✪) Morty---
---专注IT职场经验、IT技术分享的灵魂写手---
---每天带你领略IT的魅力---
---期待与您陪伴!---