Java Synchronized账户示例扣款过程未按预期顺序递减问题
问题原因分析
1. 扣减操作与打印操作没有组成原子操作
你给deduct2方法加的synchronized只能保证单次扣减操作的原子性、可见性,但你的代码中「扣减1元」和「打印余额」是两个独立的步骤:
// 这两行代码之间没有同步保护,执行间隙可以被其他线程抢占修改余额 account.deduct2(1); System.out.println("account " + account + " " + getName() +"performing....");
比如会出现如下执行时序:
- 线程A拿到锁扣减余额为9999,释放锁
- 线程B立刻抢到锁扣减余额为9998,释放锁
- 线程C立刻抢到锁扣减余额为9997,释放锁
- 此时线程A、B、C才依次执行打印逻辑,读取到的当前余额都是9997,就会出现三条余额为9997的打印记录,也就是你看到的重复、跳变情况。
2. 余额读取操作没有同步保护
Account的toString方法读取dollars变量时没有加同步,即使你在打印前刚执行完同步的扣减操作,也不能保证你读到的是你当前线程刚修改完的值——其他线程可能已经在你扣减完成释放锁之后,先一步修改了余额。
为什么最终结果符合预期?
因为扣减操作本身是被synchronized保护的原子操作,不会出现多线程同时写导致的计算错误,3个线程各执行100次扣减,总计扣减300元,所以最终10000-300=9700的结果是正确的,只有中间打印的结果不符合你的预期。
修复方案
如果需要中间打印的余额按顺序递减,只需要把「扣减+打印」两个操作放到同一个同步块中,用同一个account对象作为锁即可:
@Override public void run() { System.out.println("performing my job ...."); for (int i = 0; i < 100; i++) { // 用全局唯一的account对象做锁,包裹扣减和打印两个操作 synchronized (account) { account.deduct2(1); System.out.println("account " + account + " " + getName() +"performing...."); } } }
或者也可以把打印逻辑直接放到deduct2同步方法内部,也能达到同样的效果。
内容的提问来源于stack exchange,提问作者Harold
相关产品推荐
相关产品推荐

