Lo que debe saber sobre el flujo paralelo de Java ParallelStream

El papel de ParallelStream

El uso de subprocesos múltiples puede acelerar el procesamiento de las operaciones de recopilación. El principio subyacente es usar el grupo de subprocesos ForkJoinPool (el principio en profundidad espera que lo comparta)

¿Son los flujos paralelos necesariamente más rápidos que Stream?

El código con "parallelStream()" a veces es más lento que el código con "stream()" cuando la cantidad de datos que se procesan no es grande.
Porque: parallelStream() siempre necesita ejecutarse más que secuencialmente, dividir el trabajo entre varios subprocesos y fusionar o combinar los resultados introduce una sobrecarga significativa. Los casos de uso como convertir cadenas cortas a minúsculas son tan pequeños que son insignificantes en comparación con la sobrecarga de la división paralela.

inserte la descripción de la imagen aquí

El procesamiento de datos con varios subprocesos puede tener algunos costos de configuración inicial, como la inicialización del grupo de subprocesos. Estos gastos generales pueden amortiguar las ganancias obtenidas con el uso de estos subprocesos, especialmente si la CPU ya está muy baja en tiempo de ejecución. Además, si hay otros subprocesos que ejecutan procesos en segundo plano, etc., o si la contención es alta, el rendimiento del procesamiento paralelo se degradará aún más.

La seguridad de los hilos debe considerarse seriamente

El uso irrazonable de los tipos de datos conduce a un alto uso de la CPU

Después de que el siguiente código se ejecuta en el entorno de compilación durante un período de tiempo, el sistema muestra que el uso de la CPU del servicio es muy alto, alcanzando el 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、操作集合
            }
        });

De acuerdo con la información de registro de jstack, cuando se opera HashSet, la competencia de recursos internos conduce a un alto uso de la CPU, como se muestra en la figura a continuación.
inserte la descripción de la imagen aquí

原因: HashSet no es seguro para subprocesos. En realidad, se implementa internamente a través de HashMap. HashSet se opera en múltiples subprocesos, lo que resulta en una competencia por la conversión rojo-negro.

Excepción de puntero nulo

La lista de pares de flujo paralelo ocasionalmente informará una excepción de puntero nulo, como se muestra en la figura a continuación

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);
}

El código en sí está dividiendo varias tablas y luego ensamblando la capa empresarial. El uso de flujos paralelos puede mejorar esta operación puramente intensiva de CPU. El método predeterminado de parallelStream es usar la cantidad de núcleos de CPU del servidor como el tamaño del grupo de subprocesos.

Debido a que es un flujo paralelo, de hecho, varios subprocesos están operando el contenedor orderListVOS al mismo tiempo, pero este contenedor no puede garantizar la seguridad de los subprocesos. `

Solución

1. 推荐Use el método de agregación que viene con la secuencia, de la siguiente manera

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

2. Utilice las funciones proporcionadas por java.util.concurrent (nota: las clases relacionadas proporcionadas por este paquete tendrán bloqueos)

ParallelStream 风险

Si bien la programación de transmisión de ParallelStream brinda una gran comodidad para el desarrollo de subprocesos múltiples, también brinda una lógica implícita, que no se explica en el comentario de la interfaz:

 /**
     * 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
     */

Los anteriores son todos los comentarios de esta interfaz. La llamada lógica implícita aquí es que no todos los códigos que llaman de forma independiente a parallelStream mantendrán de forma independiente una estrategia de subprocesos múltiples, pero JDK llamará al mismo grupo de subprocesos ForkJoinPool mantenido por el entorno operativo por predeterminado. , Es decir, no importa dónde escriba list.parallelStream().forEach(); tal pieza de código, la capa inferior en realidad será ejecutada por un conjunto de grupos de subprocesos de ForkJoinPool, y el grupo de subprocesos general se ejecutará encontrará conflictos, colas, etc. El problema también se encontrará aquí y estará oculto en la lógica del código.

Lo más peligroso aquí es, por supuesto, el interbloqueo del grupo de subprocesos. Una vez que ocurre un interbloqueo, todos los lugares que llaman a parallelStream se bloquearán, sin importar si sabe si otras personas han escrito código como este.

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

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

Supongo que te gusta

Origin blog.csdn.net/lzzyok/article/details/122887687
Recomendado
Clasificación