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

Java多线程求和故障求助:sum始终为0(已加volatile仍无效)

解决Java多线程求和的线程安全与范围划分问题

看起来你遇到了两个关键问题:线程安全导致的sum值异常,以及循环区间划分错误,咱们一步步来解决。

问题1:volatile无法保证原子操作

你给sum加了volatile修饰符,它确实能保证各个线程看到的sum是最新值,但sum = sum + Sum(...)这个操作并不是原子的。它实际分为三步:

  1. 读取当前sum的值
  2. 计算sum + 子任务返回的结果
  3. 将新值写回sum

当多个线程同时执行这三步时,就会出现"丢失更新"的情况。比如线程A刚读完sum=0,还没来得及计算写入,线程B也读取了sum=0,然后各自计算完后分别写入,最终sum只会是其中一个线程的结果,而不是两者相加。这就是为什么你看到sum始终不对的核心原因。

问题2:循环区间划分逻辑错误

你的循环for (int i = start; i < end; i = i * step)会生成以下区间:

  • [1, 100)
  • [100, 10000)
  • [10000, 1000000)
  • [1000000, 100000000)

但你的目标是计算1到10000000的和,最后一个区间直接到了1亿,远远超过目标,而且最后还要单独加end(10000000),这会导致重复计算或者漏掉部分数值,结果自然不对。

解决方案

下面给你三种可行的解决方式,按推荐程度排序:

方案1:使用Callable+Future,无共享变量(最推荐)

这种方式不需要共享sum变量,每个线程计算自己的区间和,最后主线程收集所有结果汇总,完全避免线程安全问题,效率也更高。

修改后的代码:

package test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class testParallelFor {
    final static int start = 1;
    final static int end = 10000000;

    public static void main(String[] args) throws Exception {
        int num = Runtime.getRuntime().availableProcessors();
        ExecutorService exec = Executors.newFixedThreadPool(num);
        List<Future<Long>> futures = new ArrayList<>();

        try {
            // 正确划分区间:每个线程处理batchSize大小的区间
            int batchSize = (end - start + 1) / num;
            if (batchSize == 0) batchSize = 1;

            int currentStart = start;
            while (currentStart <= end) {
                int currentEnd = Math.min(currentStart + batchSize - 1, end);
                final int tempStart = currentStart;
                final int tempEnd = currentEnd;

                // 提交Callable任务,返回计算结果
                futures.add(exec.submit(new Callable<Long>() {
                    @Override
                    public Long call() {
                        return Sum(tempStart, tempEnd);
                    }
                }));

                currentStart = currentEnd + 1;
            }

            // 汇总所有结果
            long totalSum = 0;
            for (Future<Long> future : futures) {
                totalSum += future.get();
            }

            System.out.println("最终求和结果:" + totalSum);
        } finally {
            exec.shutdown();
            exec.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        }
    }

    // 修改Sum方法:计算闭区间[start, end]的和
    static long Sum(int start, int end) {
        System.out.println("Sum from " + start + " to " + end + " start.");
        long temp = 0;
        for (int k = start; k <= end; k++) {
            temp += k;
        }
        System.out.println("Sum from " + start + " to " + end + " end. sum=" + temp);
        return temp;
    }
}

方案2:使用AtomicLong保证原子操作

如果一定要用共享变量,可以用java.util.concurrent.atomic.AtomicLong,它的addAndGet方法是原子性的,能避免线程竞争问题。

修改后的关键部分:

// 替换volatile long sum = 0;
import java.util.concurrent.atomic.AtomicLong;
static AtomicLong sum = new AtomicLong(0);

// 在run方法中替换sum = sum + ...
sum.addAndGet(Sum(finalI, currentEnd));

同时也要修正区间划分的问题,和方案1的循环逻辑一致。

方案3:使用同步块

用synchronized关键字或者Lock来保护sum的更新操作,确保同一时间只有一个线程能修改sum:

// run方法中
synchronized (testParallelFor.class) {
    sum += Sum(finalI, currentEnd);
}

这种方式虽然能解决问题,但会导致线程竞争,降低多线程的效率,不如前两种方案。

验证结果

1到n的和公式是n*(n+1)/2,10000000的和应该是10000000*10000001/2 = 50000005000000,你可以用这个值来验证代码的正确性。

内容的提问来源于stack exchange,提问作者amingo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 09:37:40