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

线程间同步ArrayList未返回预期值,求排查代码问题

问题分析与解决方案

咱们先来拆解你遇到的问题,其实核心不是ArrayList的线程安全问题(你已经用synchronized保护了add操作),而是对线程结束时打印的size的理解偏差,以及一个容易忽略的同步细节:

1. 线程结束时的打印结果并非你预期的"单线程贡献值"

你的两个线程共享同一个taskList,当其中一个线程执行完1000000次add准备打印时,另一个线程大概率还在执行add操作。举个例子:

  • 线程A刚完成第1000000次add,此时列表大小是1000000
  • 但线程B已经执行了500000次add,所以线程A打印的size会是1500000,而非你预期的1000000

这不是元素被覆盖了,而是两个线程的操作在并发进行,打印动作没有和add操作同步,导致你看到的是当前时刻的总大小,而非单个线程完成添加后的状态。实际上你的代码不会丢失元素,最终列表的总大小一定是2000000。

2. 为什么替换Vector、加volatile等操作没用?

  • Vector的add是线程安全的,但同样解决不了打印size时的并发问题——size()方法本身是原子的,但从你完成最后一次add到调用size()的间隙,其他线程可能已经修改了列表。
  • volatile修饰列表引用也没用:volatile只能保证引用的可见性,不能保证列表内部状态(比如size)的原子性读取和修改的一致性,而且你已经用synchronized保护了add,可见性已经有保障了。
  • 给构造方法加同步完全没必要,因为构造方法是在主线程调用的,不存在并发问题。

如何修正代码达到你的预期?

如果你想让每个线程打印自己完成添加后的列表大小,可以用以下两种方案:

方案1:同步打印动作

把打印也放到同步块中,确保读取size时没有其他线程在修改列表:

@Override
public void run() {
    for( int i = 0; i < 1000000;i++){
        updateList(i);
    }
    // 用同一个锁保护size的读取
    synchronized (myList) {
        System.out.println("end: " + myList.size());
    }
}

这样第一个线程完成所有添加后,会先获取锁打印此时的size(1000000),第二个线程要等第一个线程释放锁后才能继续添加,等它打印时size就是2000000。

方案2:线程独立计数(更高效)

每个线程自己统计添加的数量,不需要同步打印,能更清晰看到单个线程的贡献:

class MyThread implements Runnable{
    private List<Integer> myList;
    private int ownCount = 0; // 线程独立计数
    public MyThread(List<Integer> list){
        this.myList = list;
    }
    private void updateList(int i){
        synchronized (myList) {
            myList.add(i);
        }
        ownCount++;
    }
    @Override
    public void run() {
        for( int i = 0; i < 1000000;i++){
            updateList(i);
        }
        System.out.println("我添加了" + ownCount + "个元素,当前总大小: " + myList.size());
    }
}

另外补充一个小优化:你创建了10000个线程的线程池,但只提交了2个任务,这完全没必要,线程池大小设为2就足够了,避免不必要的资源消耗。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:08:38