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

代码是否需添加synchronized?并行流操作ArrayList抛NPE咨询

问题分析与解决方案

你的思路方向是对的,但咱们得先搞清楚问题的根源,再聊聊更合适的解决办法。

首先,你遇到的NullPointerException本质原因是**ArrayList不是线程安全的集合**。在并行流中,多个线程同时调用ret.add(i)时,会破坏ArrayList内部的结构(比如size变量的竞态条件、数组扩容时的并发操作),这不仅会导致NPE,还可能出现元素丢失、重复或者数组越界等问题。

关于你提到的同步思路

用synchronized块或者Lock接口确实能解决线程安全问题——通过加锁保证同一时刻只有一个线程能执行ret.add(i),避免并发修改。但这种方式有个明显的缺点:会把并行流的并行执行逻辑变成串行,所有线程都要竞争同一把锁,完全失去了并行流提升效率的意义。

更推荐的解决方案:用Stream API自带的收集器

Stream API本身就为并行流设计了线程安全的收集机制,最适合的方式是用collect()方法替代手动往集合里添加元素。比如你可以把代码改成这样:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<Integer> ret = IntStream.range(0, 10)
                .parallel()
                .peek(i -> {
                    if (i % 2 == 0) {
                        try {
                            System.out.println("stop" + i);
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                })
                .boxed()
                .collect(Collectors.toList());

        ret.forEach(v -> System.out.println(v));
    }
}

这个方案的优势在于:

  • collect()方法在并行流中会自动分块收集结果,然后合并,全程不需要手动加锁,线程安全且能保留并行流的性能优势。
  • 代码更简洁,符合Stream API的设计理念。

如果一定要手动操作集合(不推荐)

如果因为某些特殊场景必须用ArrayList手动添加元素,那可以用synchronized或者Lock来保证安全,示例如下:

用synchronized块的写法

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

public class SynchronizedExample {
    public static void main(String[] args) {
        List<Integer> ret = new ArrayList<>();
        Object lock = new Object(); // 用专门的锁对象,比锁ret本身更安全

        IntStream.range(0, 10).parallel().forEach(i -> {
            if (i % 2 == 0) {
                try {
                    System.out.println("stop" + i);
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (lock) {
                ret.add(i);
            }
        });

        ret.forEach(v -> System.out.println(v));
    }
}

用Lock接口的写法

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;

public class LockExample {
    public static void main(String[] args) {
        List<Integer> ret = new ArrayList<>();
        Lock lock = new ReentrantLock();

        IntStream.range(0, 10).parallel().forEach(i -> {
            if (i % 2 == 0) {
                try {
                    System.out.println("stop" + i);
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            lock.lock();
            try {
                ret.add(i);
            } finally {
                lock.unlock(); // 必须在finally里释放锁,避免异常导致锁无法释放
            }
        });

        ret.forEach(v -> System.out.println(v));
    }
}

但再次强调:这两种写法会让并行流退化为串行执行,性能远不如用collect()的方案,除非万不得已不建议使用。

总结

你的同步思路能解决问题,但不是最优解。优先选择Stream API的collect()方法来收集并行流的结果,既安全又高效;如果必须手动操作非线程安全集合,再考虑用synchronized或Lock,但要接受性能上的损失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 14:53:14