Java并发:parallel()结合tryLock()为何total稳定total1却不可预测?
问题解析:total结果稳定为45,total1结果不可预测的原因
以下代码中,total的结果始终为45,但total1的结果却不可预测。明明deposit方法里没有依赖tryLock()的返回值来控制执行,为什么total不会出现线程安全问题?请解释两者的计算差异:
import java.util.concurrent.locks.*; import java.util.concurrent.atomic.*; import java.util.stream.*; public class HelloWorld{ private Lock vault = new ReentrantLock(); private int total = 0; private int total1 = 0; public void deposit(int value) { try { vault.tryLock(); total += value; } finally { vault.unlock(); } } public void add(int val) { total1 += val; } public static void main(String[] unused) { HelloWorld bank = new HelloWorld(); IntStream.range(1, 10) .parallel() .forEach(s -> bank.deposit(s)); System.out.println(bank.total); // 45 HelloWorld bank1 = new HelloWorld(); AtomicLong value1 = new AtomicLong(0); final long[] value2 = {0}; IntStream.iterate(1, i -> 1).limit(100) .parallel() .forEach(i -> value1.incrementAndGet()); IntStream.iterate(1, i -> 1).limit(100) .parallel() .forEach(i -> ++value2[0]); IntStream.iterate(1, i -> 1).limit(100) .parallel() .forEach(i -> bank1.add(i)); System.out.println(value1+" "+value2[0]+" " +bank1.total1); // 100 94(Unpredictable) 96 (Unpredictable) } }
核心差异分析
1. total结果稳定是场景巧合,并非代码正确
你看到total始终为45,不是因为同步逻辑有效,而是测试场景的特殊性导致的:
deposit方法的写法完全错误:tryLock()尝试获取锁,成功返回true、失败返回false,但代码根本没判断返回值——不管有没有拿到锁,都会执行total += value,然后在finally里调用unlock()。- 但你用的
IntStream.range(1,10)仅包含9个元素,并行流底层的ForkJoinPool线程数很少,甚至可能实际是串行执行的。这意味着每个线程调用deposit时,tryLock()都能成功获取锁,total的累加完全在锁保护下完成,自然不会出现线程安全问题,结果就是1到9的和45。 - 如果你把元素数量改成100甚至更多,
total的结果会变得不可预测,同时控制台会抛出IllegalMonitorStateException——因为线程没拿到锁时调用unlock()会触发异常,且无锁的total += value会出现竞态条件。
2. total1结果不可预测是必然的线程安全问题
add方法里的total1 += val是非原子操作,它分为三步:
- 读取
total1的当前值 - 加上
val计算新值 - 将新值写回
total1
多个线程并行执行时,会出现"读-改-写"的竞态条件:比如线程A读取了total1=5,线程B同时也读取了total1=5,两者都加1,最后写回的都是6,相当于少加了一次。这就是为什么total1的结果总是小于预期的100,且每次运行结果都不一样。
3. 正确的同步写法参考
如果要让deposit方法真正实现线程安全,必须判断tryLock()的返回值:
public void deposit(int value) { if (vault.tryLock()) { // 先判断是否获取到锁 try { total += value; } finally { vault.unlock(); // 只有拿到锁才释放 } } else { // 处理获取锁失败的逻辑,比如重试、抛出异常等 } }
内容的提问来源于stack exchange,提问作者priyanka singh rao
相关产品推荐
相关产品推荐

