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

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是非原子操作,它分为三步:

  1. 读取total1的当前值
  2. 加上val计算新值
  3. 将新值写回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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 10:10:01