Java多线程求和故障求助:sum始终为0(已加volatile仍无效)
看起来你遇到了两个关键问题:线程安全导致的sum值异常,以及循环区间划分错误,咱们一步步来解决。
问题1:volatile无法保证原子操作
你给sum加了volatile修饰符,它确实能保证各个线程看到的sum是最新值,但sum = sum + Sum(...)这个操作并不是原子的。它实际分为三步:
- 读取当前sum的值
- 计算sum + 子任务返回的结果
- 将新值写回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

