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

Java多线程并行程序耗时接近串行的原因排查

Why does multi-threaded CPU-bound code perform worse than single-threaded on Apple M1?

我来帮你拆解这个反直觉的问题——你遇到的性能瓶颈根本不是CPU调度的问题,而是你的computation()函数里隐藏了大量内存分配与垃圾回收(GC)开销,多线程场景下这些开销被急剧放大,直接拖慢了整体性能。

问题出在你的computation()函数里

先仔细看你的计算逻辑:

private static void computation() {
    long n = (long) Math.pow(10, 7);
    var sum = 0;
    for (long i = 0; i < n; i++) {
        sum += LongStream.range(1, 9).boxed().limit(n).map(l -> new BigDecimal(String.valueOf(l))).distinct().count();
    }
}

这里有两个致命的冗余点:

  • 疯狂的临时对象创建:每次循环都会生成8个BigDecimal实例,1亿次循环下来就是8亿个临时对象——这会让JVM的垃圾回收器彻底忙不过来。
  • 完全多余的Stream操作:LongStream.range(1,9)本来就只有8个不重复的元素,limit(n)和distinct()不会改变最终count()的结果(始终是8),这些操作纯粹是在浪费资源。

单线程时,GC的压力还能勉强应付;但8个线程同时疯狂分配对象,会引发连锁反应:

  1. Young GC的触发频率呈指数级上升
  2. GC的停顿时间(尤其是STW阶段)会让所有线程等待,大量CPU时间被浪费在内存回收上,而非真正的计算
  3. 多线程下的内存分配还会带来额外的同步开销,进一步拖慢速度

这就是为什么你看到CPU使用率高达790%,但耗时却接近单线程的8倍——大部分CPU时间都花在处理GC和内存同步上了。

验证猜想的简单方法

你可以做两个测试来确认这个结论:

  1. 替换无意义的计算逻辑:把computation()里的Stream操作直接换成常量8,比如:
private static void computation() {
    long n = (long) Math.pow(10, 7);
    var sum = 0;
    for (long i = 0; i < n; i++) {
        sum += 8; // 直接返回固定值
    }
}

再跑8线程测试,你会发现耗时和单线程几乎一致——因为此时没有了对象分配和GC开销,真正的CPU密集型任务可以充分利用多核心。

  1. 查看GC日志:添加JVM参数-XX:+PrintGCDetails -XX:+PrintGCTimeStamps,对比单线程和8线程的GC情况。你会发现8线程时GC的总停顿时间占比极高,这就是耗时增加的核心原因。

修复方案

如果这是模拟代码,调整计算逻辑即可;如果是实际业务场景,你需要:

  • 减少循环内的对象分配:尽量重用对象(比如用对象池)、使用基本类型代替包装类型,避免在高频循环里创建临时对象。
  • 选择合适的GC:对于多线程下的大内存分配场景,Java 11+的ZGC或者Shenandoah GC是更好的选择,它们的停顿时间更短,对多线程性能影响更小。
  • 优化计算逻辑:像你代码里的冗余Stream操作,一定要提前剔除,避免做无意义的工作。

另外你提到绑定核心没有改善,这很正常——因为问题根本不在CPU核心的调度,而是内存和GC的瓶颈,所以绑定核心解决不了本质问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 17:50:22