Java简单同步问题:为何线程测试未出现预期交错结果?
为什么你的多线程Counter测试始终输出0?
这是个很常见的多线程入门坑,核心原因是你的测试代码里每个线程只执行了一次操作,竞态条件出现的概率低到几乎可以忽略,所以你看不到非预期的结果。具体拆解下:
- 单次操作的调度特性:虽然
c++和c--本质是「读取-修改-写入」三个非原子步骤,但当每个线程只做一次的时候,操作系统的线程调度很难刚好卡在这三个步骤之间切换线程。大部分情况下,线程t1会完整跑完increment(),再轮到t2执行decrement(),或者反过来,最终结果自然是0。 - 隐式同步的干扰:你在
run()里加的System.out.println(counter.value())其实自带了隐式同步(System.out的println方法是加了锁的),这反而进一步降低了线程交错的概率,让结果更稳定。
怎么复现竞态条件?
要看到非预期的错误结果,你需要让线程重复执行多次操作,比如循环10000次,这样线程调度的交错概率会大大提升。修改后的测试代码如下:
public class TestPara { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); AdderCounter ac = new AdderCounter(counter); SubberCounter sc = new SubberCounter(counter); Thread t1 = new Thread(ac); Thread t2 = new Thread(sc); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("最终结果: " + counter.c); } } class AdderCounter implements Runnable { Counter counter; public AdderCounter(Counter counter) { this.counter = counter; } @Override public void run() { // 循环执行10000次递增 for (int i = 0; i < 10000; i++) { counter.increment(); } } } class SubberCounter implements Runnable { Counter counter; public SubberCounter(Counter counter) { this.counter = counter; } @Override public void run() { // 循环执行10000次递减 for (int i = 0; i < 10000; i++) { counter.decrement(); } } } class Counter { int c = 0; public void increment() { c++; } public void decrement() { c--; } public int value() { return c; } }
运行这段代码,你会发现最终结果大概率不是0,而是一个随机的负数或正数——这就是竞态条件导致的错误。
怎么解决这个问题?
按照Oracle文档的建议,你可以通过两种方式修复:
- 给
Counter的方法添加synchronized修饰,保证同一时间只有一个线程能执行这些方法:
class Counter { int c = 0; public synchronized void increment() { c++; } public synchronized void decrement() { c--; } public synchronized int value() { return c; } }
- 使用
java.util.concurrent.atomic.AtomicInteger替代普通int,它本身就是线程安全的原子类,无需手动加锁:
import java.util.concurrent.atomic.AtomicInteger; class Counter { AtomicInteger c = new AtomicInteger(0); public void increment() { c.incrementAndGet(); } public void decrement() { c.decrementAndGet(); } public int value() { return c.get(); } }
修改后再运行测试代码,最终结果就会稳定为0了。
内容的提问来源于stack exchange,提问作者Kent
相关产品推荐
相关产品推荐

