面试题答案
一键面试导致性能未达预期甚至下降的陷阱
- 数据量过小:并行流的并行化操作涉及线程创建、任务划分等开销。若数据量小,这些开销可能超过并行处理带来的收益。例如只有几个元素的集合使用并行流,其性能反而不如顺序流。
- 任务粒度不均:并行流将任务划分成多个子任务并行执行。若子任务处理时间差异大,会出现负载不均衡,部分线程早早完成任务,而部分线程长时间忙碌,整体性能无法有效提升。比如在并行处理数组时,不同元素的处理逻辑复杂程度差异很大。
- 共享状态与可变对象:并行流执行过程中若操作涉及共享可变状态,如多个线程同时修改同一个可变对象,会导致数据竞争和线程安全问题,不仅影响性能,还可能导致结果错误。例如并行流中使用共享计数器来统计符合条件的元素个数。
- 复杂的中间操作:一些复杂的中间操作(如
distinct
、sorted
等)在并行流中实现成本较高。因为这些操作可能需要全局数据信息,并行执行时需要额外的同步和协调,增加开销。例如在大数据集上并行执行distinct
操作。 - 流操作链过长:并行流操作链过长,会增加整体的计算复杂度和线程管理开销。每一个中间操作都会增加任务调度和数据传递的成本,最终可能影响性能。
应对策略
- 合理评估数据量:在使用并行流前,先对数据量进行评估。可以通过性能测试,确定在特定数据规模下,并行流是否能带来性能提升。对于小数据量,优先使用顺序流。
- 优化任务粒度:尽量使并行处理的任务粒度均匀。可以根据数据特点进行预分组,将相似复杂度的任务分配到同一组,让每个线程的工作量大致相同。例如在并行处理任务时,按任务复杂程度进行分类,然后再分配到不同线程。
- 避免共享可变状态:使用不可变对象和线程安全的数据结构。若需要统计结果,可使用线程安全的累加器(如
AtomicInteger
),或者利用Collectors
提供的规约操作,这些操作是线程安全且高效的。例如使用Collectors.summingInt
来统计总和。 - 简化中间操作:尽量避免在并行流中使用复杂的中间操作,若必须使用,考虑先对数据进行预处理简化操作,或者在并行处理后再执行复杂操作。例如先在顺序流中进行部分简单过滤,再并行处理,最后进行
distinct
操作。 - 拆分长操作链:将长的流操作链拆分成多个较短的操作链,分别执行并行处理,最后合并结果。这样可以降低每个并行任务的复杂度,减少线程管理开销。