You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 07:06:04