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

为什么Java多线程模拟售票未出现重复售票、超卖等线程安全错误

现象成因

你当前没有出现重复售票/超卖问题,本质是运行环境的巧合,不代表代码是线程安全的,具体原因如下:

  • 执行速度过快:sell()方法的逻辑非常精简,单线程一次调用耗时极短,在CPU时间片分配的粒度下,单个线程很容易连续多次抢到执行权限,直到时间片耗尽才会切换到其他线程,所以你会看到输出里A、B线程经常连续输出多条售票记录,很少出现交叉冲突。
  • 未触发指令重排和竞争条件:你的测试用例总票数只有50,三个线程各执行50次,整体执行轮次很少,刚好没有出现多个线程同时进入if (number > 0)判断的场景。
  • 解释执行阶段未触发优化:如果代码运行时间更长,JIT编译后会进行指令重排,没有同步修饰的number变量的可见性、操作原子性问题会更容易暴露。

复现线程安全问题的方法

你只需要给sell()方法加入微小的时间延迟,放大竞争窗口,立刻就能看到重复售票、超卖的问题:

public void sell() {
    if (number > 0) {
        // 加入10ms延迟,模拟业务处理耗时,放大线程竞争窗口
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " selled " + number-- + ", remain " + number);
    }
}

正确的线程安全写法

如果要避免售票问题,需要给操作加同步,可选方案如下:

// 方案1:给sell方法加synchronized同步修饰
public synchronized void sell() {
    if (number > 0)
        System.out.println(Thread.currentThread().getName() + " selled " + number-- + ", remain " + number);
}

// 方案2:使用原子类保证number操作的原子性
class Ticket {
    private AtomicInteger number = new AtomicInteger(50);

    public void sell() {
        int cur = number.get();
        if (cur > 0) {
            int remain = number.decrementAndGet();
            if (remain >=0) {
                System.out.println(Thread.currentThread().getName() + " selled " + cur + ", remain " + remain);
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Hao

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.15 15:54:11