Java多线程使用join()计算1-100000求和结果异常问题排查
这是典型的多线程竞态条件问题,核心原因是共享变量的非原子操作导致的线程安全问题,我来给你拆解分析下:
为什么会出现错误结果?
你的Calculation类中的total是多个线程共享的变量,而total += i这个操作不是原子操作——它实际上被拆分成了三个步骤:
- 读取
total的当前值 - 将读取的值与
i相加,得到新值 - 将新值写回
total
当两个线程同时执行这三步时,就会出现“覆盖”问题:比如线程A读取了total=100,线程B也同时读取了total=100;线程A计算后写回100+5=105,线程B计算后写回100+3=103,最终total的值是103,而不是预期的108。这种情况会随着计算量增大(比如1到100000)变得频繁,导致结果偏小且每次都不同;而小数值计算时,线程执行速度快,调度冲突概率低,所以不容易暴露问题。单线程时没有竞争,自然结果正确。
几种可行的解决方案
方案1:给共享方法加同步锁
给calcSum方法加上synchronized修饰符,确保同一时间只有一个线程能执行这个方法,避免竞态条件:
class Calculation { private long total=0; public synchronized void calcSum(long start, long end) { long i = start; for( ;i<=end; i++) { total += i; } } public long getTotal() { return total; } }
这种方式简单直接,适合小规模计算场景,缺点是会带来一定的锁竞争开销,但对于你的需求来说完全够用。
方案2:使用原子类替代普通变量
Java提供了java.util.concurrent.atomic包下的原子类,它们的操作都是原子性的,不需要手动加锁。把total换成AtomicLong,用addAndGet方法替代+=操作:
import java.util.concurrent.atomic.AtomicLong; class Calculation { private AtomicLong total = new AtomicLong(0); public void calcSum(long start, long end) { long i = start; for( ;i<=end; i++) { total.addAndGet(i); } } public long getTotal() { return total.get(); } }
这种方式比synchronized更轻量,适合单个变量的原子更新场景。
方案3:线程私有局部和,最后合并
让每个线程计算自己负责区间的局部和,最后在主线程中把两个局部和相加,完全避免共享变量的修改:
class CalculatorThread extends Thread{ private long start; private long end; private long threadSum = 0; // 线程私有变量,无竞争 public CalculatorThread(long start, long end) { this.start = start; this.end = end; } @Override public void run() { long i = start; for( ;i<=end; i++) { threadSum += i; } } public long getThreadSum() { return threadSum; } } public class ParallelTest { public static void main(String[] args) throws InterruptedException { int start = 1; int end = 100000; CalculatorThread ct1 = new CalculatorThread(start, end/2); CalculatorThread ct2 = new CalculatorThread( (end/2) + 1, end); ct1.start(); ct2.start(); ct1.join(); ct2.join(); System.out.println(ct1.getThreadSum() + ct2.getThreadSum()); } }
这种方式性能最优,因为没有任何锁竞争,是多线程计算场景中推荐的思路——尽量减少共享状态,让线程各自处理独立的任务,最后合并结果。
内容的提问来源于stack exchange,提问作者user54321

