What you should know about Java parallel stream ParallelStream

The role of parallelStream

Using multi-threading can speed up the processing of collection operations. The underlying principle is to use the thread pool ForkJoinPool (the in-depth principle looks forward to your sharing)

Are parallel streams necessarily faster than Stream?

Code with "parallelStream()" is sometimes slower than code with "stream()" when the amount of data being processed is not large.
Because: parallelStream() always needs to execute more than sequentially, splitting the work between multiple threads and merging or combining the results introduces significant overhead. Use cases like converting short strings to lowercase are so small that they are negligible compared to the overhead of parallel splitting.

insert image description here

Processing data with multiple threads may have some initial setup costs, such as initializing the thread pool. These overheads may dampen the gains gained from using these threads, especially if the CPU is already very low at runtime. Also, if there are other threads running background processes, etc., or if contention is high, then the performance of parallel processing will degrade even further.

Thread safety should be seriously considered

Unreasonable use of data types leads to high CPU usage

After the following code runs in the build environment for a period of time, the system shows that the CPU usage of the service is very high, reaching 100%.

        Set<TruckTeamAuth> list = new HashSet<>();  // 1、声明变量
        List<STruckDO> sTruckDOList = isTruckService.lambdaQuery().select(STruckDO::getId, STruckDO::getTeamId).isNotNull(STruckDO::getTeamId).in(STruckDO::getTeamId, teamIdList).list();
        sTruckDOList.parallelStream().forEach(t -> {
    
     // 2、并行处理
            if (StrUtil.isNotBlank(t.getId()) && StrUtil.isNotBlank(t.getTeamId())) {
    
    
                list.add(TruckTeamAuth.builder().teamId(t.getTeamId()).truckId(t.getId()).build()); // 3、操作集合
            }
        });

According to the log information of jstack, when operating HashSet, internal resource competition leads to high CPU usage, as shown in the figure below
insert image description here

原因: HashSet is non-thread-safe. It is actually implemented internally through HashMap. HashSet is operated in multiple threads, resulting in competition for red-black conversion.

null pointer exception

The parallel stream pair list will occasionally report a null pointer exception, as shown in the figure below

List<OrderListVO> orderListVOS = new LinkedList<OrderListVO>();
 
baseOrderBillList.parallelStream().forEach(baseOrderBill -> {
    
    
   OrderListVO orderListVO = new OrderListVO();
   // 设置order中的属性
 
   orderListVO.setOrderbillgrowthid(baseOrderBill.getOrderbillgrowthid());
   orderListVO.setOrderbillid(baseOrderBill.getOrderbillid());
   ……
   orderListVOS.add(orderListVO);
}

The code itself is doing multi-table splitting and then assembling the business layer. Using parallel streams can improve this purely CPU-intensive operation. The default method of parallelStream is to use the number of server CPU cores as the thread pool size.

Because it is a parallel flow, in fact, multiple threads are operating the orderListVOS container concurrently, but this container cannot guarantee thread safety. `

Solution

1. 推荐Use the aggregation method that comes with stream, as follows

 orderListVOS.parallelStream()
                .sorted(Comparator.comparing(OrderListVO::getCreatetime).reversed())
                .collect(Collectors.toList());

2. Use the features provided by java.util.concurrent (note: the related classes provided by this package will have locks)

ParallelStream 风险

Although the streaming programming of parallelStream brings great convenience for multi-threaded development, it also brings an implicit logic, which is not explained in the interface comment:

 /**
     * Returns a possibly parallel {@code Stream} with this collection as its
     * source.  It is allowable for this method to return a sequential stream.
     *
     * <p>This method should be overridden when the {@link #spliterator()}
     * method cannot return a spliterator that is {@code IMMUTABLE},
     * {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
     * for details.)
     *
     * @implSpec
     * The default implementation creates a parallel {@code Stream} from the
     * collection's {@code Spliterator}.
     *
     * @return a possibly parallel {@code Stream} over the elements in this
     * collection
     * @since 1.8
     */

The above are all the comments of this interface. The so-called implicit logic here is that not every code that independently calls parallelStream will independently maintain a multi-threaded strategy, but JDK will call the same ForkJoinPool thread pool maintained by the operating environment by default. , That is to say, no matter where you write list.parallelStream().forEach(); such a piece of code, the bottom layer will actually be run by a set of ForkJoinPool thread pools, and the general thread pool will encounter conflicts, queuing, etc. The problem will also be encountered here, and will be hidden in the code logic.

The most dangerous thing here is of course the deadlock of the thread pool. Once a deadlock occurs, all places that call parallelStream will be blocked, no matter whether you know whether other people have written code like this.

以这段代码为例
list.parallelStream().forEach(o -> {
    
    
    o.doSomething();
    ...
});

只要在doSomething()中有任何导致当前执行被hold住的情况,则由于parallelStream完成时会执行join操作,任何一个没有完成迭代都会导致join操作被hold住,进而导致当前线程被卡住。
典型的操作有:线程被wait,锁,循环锁,外部操作(访问网络)卡住等。

Guess you like

Origin blog.csdn.net/lzzyok/article/details/122887687
Recommended