Java中非线程安全计数器为何总能返回正确值?
为什么非线程安全计数器在10线程递增后总能返回正确值?
你的代码里的Counter确实是非线程安全的——counter +=1本质是读取当前值→加1→写回新值三个非原子操作,多个线程同时执行时可能出现值覆盖,导致最终计数小于预期。但你没看到错误结果,原因在于:
1. 单次递增的竞争窗口极小
每个线程只执行一次递增操作,现代CPU执行这三个步骤的速度极快,10个线程的单次操作很难刚好在同一时间点竞争同一资源。线程切换本身也需要开销,10次操作可能在切换发生前就全部完成了,自然不会出现冲突。
2. 测试规模太小
仅10次递增的情况下,出现线程竞争的概率极低。就像扔10次硬币全是正面,不代表硬币没有反面,只是样本量不够,没触发小概率事件。
如何复现预期的错误
要触发线程安全问题,需要放大竞争概率,比如:
- 让每个线程执行多次递增(比如1000次)
- 增加线程数量
- 在递增操作前后加入微小延迟,强制线程切换
修改后的Main代码示例:
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(10); CountDownLatch startSignal = new CountDownLatch(10); CountDownLatch doneSignal = new CountDownLatch(10); Counter counter = new Counter(); for (int i=0; i<10; i++) { executorService.submit(() -> { try { startSignal.countDown(); startSignal.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } // 每个线程递增1000次,放大竞争概率 for (int j = 0; j < 1000; j++) { counter.incrementCounter(); // 可选:加入微小延迟,增加线程切换概率 // try { Thread.sleep(1); } catch (InterruptedException e) {} } doneSignal.countDown(); }); } doneSignal.await(); System.out.println("Finished: " + counter.getCounter()); // 结果会小于10000 executorService.shutdownNow(); } }
运行这段代码,你会发现最终计数大概率小于10000,这就是线程竞争导致的值覆盖问题。
总结
非线程安全的问题是概率性的,不是每次执行都会出现。你的初始测试场景因为操作次数少、执行速度快,刚好没触发竞争条件,但这不代表Counter是线程安全的。要验证线程安全问题,需要设计能放大竞争的测试用例。
内容的提问来源于stack exchange,提问作者adamjmarkham
相关产品推荐
相关产品推荐

