procesamiento - ¿Puede un Java 8 `Stream` ser paralelo sin que lo pidas?
stream java 8 ejemplo (5)
Bueno, responde a sí mismo ...
Después de pensarlo un poco más en serio (imagínate, tales cosas solo suceden después de que realmente hago la pregunta), en realidad se me ocurrió una razón por la cual ...
Las operaciones intermedias NO pueden ser seguras para hilos; como tal, la API no puede hacer suposiciones, por lo tanto, si el usuario desea una transmisión paralela, debe solicitarla explícitamente y asegurarse de que todas las operaciones intermedias utilizadas en la transmisión sean seguras para la ejecución de subprocesos.
Sin embargo, hay un caso un tanto engañoso de Collector
s; dado que un Collector
no puede saber por anticipado si se llamará como una operación de terminal en un flujo que es paralelo o no, el contrato deja en claro que "solo para estar seguro", cualquier Collector
debe ser seguro para subprocesos.
Tal como lo veo, el código obvio, al usar Java 8 Stream
s, ya sean streams "object" o streams primitivos (es decir, IntStream
y amigos) sería simplemente usar:
someStreamableResource.stream().whatever()
Pero luego, bastantes "recursos .parallelStream()
" también tienen .parallelStream()
.
Lo que no está claro al leer el javadoc es si las .stream()
siempre son secuenciales, y si las secuencias .parallelStream()
son siempre paralelas ...
Y luego está Spliterator
, y en particular its .characteristics()
, uno de ellos es que puede ser CONCURRENT
, o incluso IMMUTABLE
.
Mi intuición es que, de hecho, si un Stream
puede ser, o no, paralelo por defecto, o paralelo en absoluto, está guiado por su Spliterator
subyacente ...
¿Estoy en el camino correcto? He leído y leído nuevamente los javadocs, y todavía no puedo encontrar una respuesta clara a esta pregunta ...
Esta appart no es una especificación restringida en este momento, sin embargo, la respuesta corta es NO . Existen las funciones parallelStream()
y stream()
pero eso solo le proporciona formas de acceder a implementaciones paralelas o secuenciales de operaciones básicas comunes para procesar la secuencia. Actualmente, el tiempo de ejecución no puede suponer que sus operaciones son seguras para hilos sin el uso explícito de la llamada parallelStream()
o parallel()
, entonces la implementación predeterminada de stream()
es tener un comportamiento secuencial.
La API no tiene mucho que decir al respecto:
Los flujos se crean con una opción inicial de ejecución secuencial o paralela. (Por ejemplo, Collection.stream () crea una secuencia secuencial, y Collection.parallelStream () crea una secuencia paralela).
Con respecto a su línea de razonamiento de que algunas operaciones intermedias pueden no ser seguras para subprocesos, es posible que desee leer el resumen del paquete . El resumen del paquete analiza las operaciones intermedias, stateful vs stateless, y cómo usar correctamente un Stream
en cierta profundidad.
En general, se desaconsejan los efectos secundarios en los parámetros de comportamiento para las operaciones de flujo, ya que a menudo pueden conducir a violaciones involuntarias del requisito de apatridia, así como a otros riesgos de seguridad de las hebras.
Los parámetros de comportamiento son los argumentos dados a las operaciones intermedias sin estado.
la API no puede hacer suposiciones
La API puede hacer cualquier suposición que desee. La responsabilidad recae sobre el usuario de la API para cumplir con esas suposiciones. Sin embargo, las suposiciones pueden limitar la usabilidad. Stream
API desalienta la creación de una operación intermedia sin estado que no sea segura para subprocesos. Dado que se desaconseja en lugar de prohibirse, la mayoría de Stream
s serán secuenciales "por defecto".
Se menciona aquí : "Cuando creas una transmisión, siempre es una transmisión en serie a menos que se especifique lo contrario". Y aquí : "Se permite que este método ( parallelStream
) devuelva un flujo secuencial".
CONCURRENT
e IMMUTABLE
no están (directamente) relacionados con esto. Especifican si la colección subyacente se puede modificar sin convertir el spliterator en inválido o si es inmutable, respectivamente. La característica de spliterator que define el comportamiento de parallelStream es trySplit
. Las operaciones de terminal en una secuencia paralela invocarán eventualmente trySplit
, y cualquiera que sea la implementación, al final del día definirá qué partes, si las hay, de los datos se procesan en paralelo.
Primero, a través de la lente de especificación . Si una secuencia es paralela o secuencial es parte del estado de una secuencia. Los métodos de creación de flujo deberían especificar si crean una secuencia secuencial o paralela (y la mayoría en el JDK sí), pero no están obligados a decirlo. Si su fuente de transmisión no dice, no asuma. Si alguien te pasa una transmisión, no asumas.
Se permite que las transmisiones paralelas vuelvan a ser secuenciales a su criterio (ya que una implementación secuencial es una implementación paralela, solo una potencialmente imperfecta); Lo contrario no es verdad.
Ahora, a través de la lente de la implementación . En los métodos de creación de flujos en Colecciones y otras clases de JDK, nos atenemos a la disciplina de "crear una secuencia secuencial a menos que el usuario solicite explícitamente el paralelismo". (Otras bibliotecas, sin embargo, toman decisiones diferentes. Si son amables, especificarán su comportamiento).
La relación entre el paralelismo de flujo y Spliterator solo va en una dirección. Un Spliterator puede negarse a dividirse, negando efectivamente cualquier paralelismo, pero no puede exigir que un cliente lo divida. Por lo tanto, un Spliterator poco cooperativo puede socavar el paralelismo, pero no determinarlo.