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

Java synchronized同步ArrayList时size与stream.count()结果不一致问题

问题分析与解答

核心原因:主线程未等待子线程执行完毕

你启动101个线程执行add操作后,主线程没有做任何等待,直接就去读取list.size()。这时候部分子线程可能还没来得及执行完synchronized块里的add操作,所以size()返回的是当前时刻已经完成添加的元素数量,自然会出现小于101的情况。

为什么累加结果是5050?

这是执行时机带来的巧合:主线程执行完size()后,到执行stream().reduce()这段时间里,那些还没完成add的子线程刚好跑完了所有添加操作。等stream遍历的时候,List里已经有了全部101个元素,所以累加和刚好是0到100的总和5050。

为什么改用list.stream().count()结果正确?

同样是时机问题:当你把size()换成stream().count()时,主线程执行这个方法的时刻,所有子线程已经完成了add操作(或者刚好在执行count前全部完成)。本质上还是主线程没有等待子线程的问题,stream().count()本身并没有额外的同步机制,只是它执行的时机刚好赶上了所有元素添加完成。

正确的解决方式

要确保主线程等待所有子线程执行完毕,可以用Thread.join()或者CountDownLatch:

方式1:用Thread.join()

import java.util.ArrayList;
import java.util.List;

public class safeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 101; i++) {
            int finalI = i;
            Thread t = new Thread(() -> {
                synchronized (list) {
                    list.add(Integer.toString(finalI));
                }
            });
            threads.add(t);
            t.start();
        }
        // 等待所有线程执行完毕
        for (Thread t : threads) {
            t.join();
        }
        System.out.println(list.size()); // 必为101
        String string = list.stream()
                .map(Integer::valueOf)
                .reduce(0, Integer::sum)
                .toString();
        System.out.println(string); // 必为5050
    }
}

方式2:用CountDownLatch

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class safeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(101);
        for (int i = 0; i < 101; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    synchronized (list) {
                        list.add(Integer.toString(finalI));
                    }
                } finally {
                    latch.countDown();
                }
            }).start();
        }
        // 等待所有线程执行完毕
        latch.await();
        System.out.println(list.size()); // 必为101
        String string = list.stream()
                .map(Integer::valueOf)
                .reduce(0, Integer::sum)
                .toString();
        System.out.println(string); // 必为5050
    }
}

另外,虽然你用synchronized保证了add操作的原子性,但ArrayList本身的size()方法没有同步修饰,所以主线程读取size时,没有内存可见性保证(可能读到的是缓存中的旧值)。不过在这个场景里,核心问题还是线程执行顺序的时机,而非内存可见性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 07:18:41