面试题答案
一键面试变化分析
- 执行顺序:在并行流中,
foreach
方法不再保证元素按照流中原始顺序执行。因为并行流会将任务拆分并由多个线程并行处理,每个线程处理不同的元素子集,所以元素的处理顺序是不确定的。 - 执行效率:理论上,并行流的
foreach
可以利用多核CPU的优势,加快对大量元素的处理速度。多个线程同时处理不同部分的流数据,相比单线程顺序处理会更高效。
可能出现的问题
- 线程安全问题:如果在
foreach
操作中对共享资源进行读写操作,可能会导致数据竞争和不一致。例如,多个线程同时修改同一个集合,可能会破坏集合的结构或导致数据丢失。 - 结果无序性:由于执行顺序不确定,如果业务逻辑依赖元素的顺序,那么并行流
foreach
的结果可能不符合预期。
避免方法
- 线程安全处理:
- 使用线程安全的数据结构:如
ConcurrentHashMap
、CopyOnWriteArrayList
等。例如,如果要在foreach
中往集合添加元素,可以使用CopyOnWriteArrayList
。
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(); Stream.of(1, 2, 3).parallel().forEach(list::add);
- 使用同步机制:如
synchronized
关键字或Lock
接口。但这种方式可能会降低并行流的性能优势,因为会引入线程间的同步开销。
List<Integer> sharedList = new ArrayList<>(); Object lock = new Object(); Stream.of(1, 2, 3).parallel().forEach(i -> { synchronized (lock) { sharedList.add(i); } });
- 使用线程安全的数据结构:如
- 处理结果无序问题:
- 不依赖顺序的操作:如果业务逻辑允许,可以将操作设计成不依赖元素顺序。例如,对元素进行独立的计算操作,如求平方和等。
- 使用
forEachOrdered
:如果必须保证顺序,可以使用forEachOrdered
方法,它会按照流中元素的顺序执行操作,但这样会部分牺牲并行流的性能优势,因为在一定程度上会限制并行处理。
Stream.of(1, 2, 3).parallel().forEachOrdered(System.out::println);