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

Java多线程Leibniz级数计算π结果异常,单线程正常

问题分析与修复

核心原因:条件收敛级数的并行求和问题

Leibniz公式对应的级数是条件收敛级数,这类级数的特点是:改变项的求和顺序会导致最终结果发生变化。单线程下严格按照顺序累加项,结果正确;但多线程并行时,多个线程随机地对共享的pi变量进行加减操作,相当于打乱了级数的求和顺序,因此每次运行的结果都会随机变化。

代码中的其他线程安全问题

除了级数本身的特性问题,代码还存在以下线程安全缺陷:

  • 静态变量flag的修改与访问未同步:多个线程同时修改flag,存在竞态条件,且主线程提交任务时的PICal.flag判断无法保证线程及时停止。
  • 线程未在循环中检查停止信号:线程发现term小于阈值后仅设置flag就返回,但其他线程不会主动检查flag状态,会继续累加不需要的小项,导致结果错误。
  • 共享变量pi的依赖:虽然job方法用synchronized保证了原子修改,但无法解决求和顺序错误的核心问题。

修复方案:分区间独立计算后汇总

要在多线程下正确计算,需保证每个线程独立计算自己负责的连续项的子和(按顺序累加),最后将所有子和汇总。这样既利用多线程提升效率,又保证级数的求和顺序正确。

修改后的代码示例:

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
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;

public class PiCalculator {
    public static class PICal implements Callable<BigDecimal> {
        private final long start;
        private final long end;
        private final MathContext mc;

        public PICal(long start, long end, MathContext mc) {
            this.start = start;
            this.end = end;
            this.mc = mc;
        }

        @Override
        public BigDecimal call() {
            BigDecimal subSum = BigDecimal.ZERO;
            boolean add = start % 2 == 0; // 第start项的符号:偶数项加,奇数项减
            for (long i = start; i < end; i++) {
                BigDecimal term = BigDecimal.ONE.divide(BigDecimal.valueOf(2L * i + 1), mc);
                if (add) {
                    subSum = subSum.add(term);
                } else {
                    subSum = subSum.subtract(term);
                }
                add = !add;
            }
            return subSum;
        }
    }

    public static void main(String[] args) {
        int floatingPoint = 8;
        BigDecimal base = new BigDecimal("0.1");
        BigDecimal floatingPointThreshold = base.pow(floatingPoint + 1);
        MathContext mc = new MathContext(floatingPoint + 1);

        // 预先计算需要迭代的最大次数
        BigDecimal maxDenominator = BigDecimal.ONE.divide(floatingPointThreshold, mc);
        long maxIter = (maxDenominator.longValue() - 1) / 2;

        int threadCount = 100;
        long power = maxIter / threadCount;
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        List<Future<BigDecimal>> futures = new ArrayList<>();

        // 分配任务区间
        for (int i = 0; i < threadCount; i++) {
            long start = i * power;
            long end = (i == threadCount - 1) ? maxIter : (i + 1) * power;
            futures.add(executor.submit(new PICal(start, end, mc)));
        }

        // 汇总所有子和
        BigDecimal piSum = BigDecimal.ZERO;
        for (Future<BigDecimal> future : futures) {
            try {
                piSum = piSum.add(future.get());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        executor.shutdown();
        BigDecimal pi = piSum.multiply(BigDecimal.valueOf(4)).setScale(floatingPoint, RoundingMode.FLOOR);
        System.out.println(pi);
    }
}

修改说明:

  1. 用Callable代替Runnable:每个线程返回自己计算的子和,避免共享变量的竞态条件。
  2. 预先计算迭代次数:根据阈值算出需要处理的最大i,避免线程中途判断的混乱。
  3. 分区间独立计算:每个线程处理连续的项区间,保证子区间内的求和顺序正确,最终汇总结果符合Leibniz公式的要求。
  4. 移除共享静态变量:所有线程的计算状态独立,仅在最后汇总结果,彻底解决线程安全问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 11:40:02