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

线程竞态条件:静态整数num的最小与最大取值范围疑问

为什么静态变量num的最小值可以低于5?

嘿,这个问题确实容易踩坑!你说的最大值25是完全正确的——当所有num++操作都顺顺利利没有竞态,每个线程的五次自增都依次执行、互不干扰时,5个线程×5次自增=25,最终num就是25。

但关于最小值,面试官说的没错,它确实可能低于你认为的5,核心原因在于两个关键点:num++不是原子操作,以及**num没有可见性保证**。

先搞懂num++到底在干啥

num++看起来是个单一操作,但在JVM层面其实拆成了三步:

  • 把当前num的数值读到寄存器里
  • 把寄存器里的值加1
  • 把新值写回内存

这三步之间完全可以被其他线程打断,比如5个线程同时读到num=0,然后各自加1变成1,再依次写回内存——这5次操作最后只让num从0变成1,等于白做了4次。

更关键的:num没加volatile,线程之间看不到彼此的修改

因为num没有被volatile修饰,Java内存模型不强制线程之间的变量可见性:

  • 线程A修改了num,线程B可能永远看不到这个变化——线程B可以一直用自己缓存里的旧值,完全不主动从主存刷新。
  • 注意:线程自己的修改在自身线程内是可见的(比如线程A执行五次自增,它自己肯定能看到num从0涨到5),但其他线程完全可以无视主存里的最新值。

举个极端例子:最终num=1

给你讲一个极端但完全符合Java内存模型的场景:

  1. 初始时主存里num=0,所有5个线程都把0加载到自己的缓存里。
  2. 线程1先执行第一次num++:读0→加1→写1到主存,这时候主存num=1,但线程2到5的缓存还是0,它们没主动刷新,所以根本看不到这个变化。
  3. 线程2到5接着执行第一次num++:都读自己缓存里的0,加1变成1,然后写回主存——每次写回都会把主存的1覆盖成1,等于第一次循环结束后num还是1。
  4. 第二次循环开始:线程1读自己缓存里的1(自己的修改自己能看到),加1变成2写回主存;但线程2到5还是读缓存里的0,加1变成1写回,又把主存的2盖回1。
  5. 第三次到第五次循环全是这个套路:每次线程1好不容易把num改成2,其他线程马上给盖回1。最后五次循环结束,num=1,这不就远低于5了嘛。

甚至还有更极端的情况:如果所有线程在整个五次循环里都死死抱着缓存里的0不放,每次自增都写回1,那最终num就停在1——这完全合规,因为Java没有规定线程必须定期刷新缓存去读主存的最新值。

总结一下:没有可见性保证的情况下,线程之间的修改可以完全互不感知,导致大量自增操作被无效覆盖,num的最小值理论上可以低到1。

内容的提问来源于stack exchange,提问作者Anmol Singh Jaggi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:15:47