代码是否需添加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
相关产品推荐
相关产品推荐

