Skip to content

Commit 3af28e6

Browse files
committed
feat(parallel-streams): 6 exemplos
Adicionando 6 exemplos de Streams paralelos, principalmente relacionados a operações de reduce e collect. Ref. Issue #24
1 parent 43610f3 commit 3af28e6

File tree

7 files changed

+234
-7
lines changed

7 files changed

+234
-7
lines changed

book/05-java-streams/sections/02-parallel-streams.asc

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,111 @@ Tempo stream paralelo: 1479
9191
+
9292
Perceba que na máquina onde o código foi executado, a execução em paralelo demorou apenas 15% do tempo da execução sequencial. Esse não é um teste minucioso, mas mostra o potencial de _Streams_ paralelos.
9393

94-
. Evitar expressões lambda que alteram o estado de um objeto. Exemplo de forEachOrdered e um map com operação stateful.
95-
. Exemplo de findAny com resultados imprevisíveis
96-
. Utilização de unordered para ganhar performance em findFirst, limit, skip
97-
. Reduce com acumuladores associativos (multiplicação)
98-
. Reduce com acumuladores não-associativos (subtração)
99-
. Exemplo com toConcurrentMap
100-
. Exemplo com groupByConcurrent
94+
. Operações intermediárias que alteram o estado de objetos podem gerar resultados inesperados ao serem executadas em paralelo.
95+
+
96+
[source,java,indent=0]
97+
.{java-package}/parallelstreams/Streams_ParallelStatefulOperation.java
98+
----
99+
include::{section-java-package}/parallelstreams/Streams_ParallelStatefulOperation.java[tag=code]
100+
----
101+
+
102+
.Saída no console
103+
[source,console]
104+
----
105+
Ordem no forEachOrdered:
106+
A
107+
B
108+
C
109+
Ordem na synchronizedList:
110+
A
111+
C
112+
B
113+
----
114+
+
115+
Perceba que a ordem foi respeitada na última operação do _Stream_, o `forEachOrdered`, mas não foi respeitada na execução da operação intermediária `map`. Isso ocorre porque essa operação intermediária não precisa seguir a ordem dos itens do stream.
116+
117+
. A operação `findAny` pode trazer qualquer resultado em uma operação paralela.
118+
+
119+
[source,java,indent=0]
120+
.{java-package}/parallelstreams/Streams_ParallelFindAny.java
121+
----
122+
include::{section-java-package}/parallelstreams/Streams_ParallelFindAny.java[tag=code]
123+
----
124+
+
125+
.Saída no console
126+
[source,console]
127+
----
128+
findAny Sequencial: 7
129+
findAny Paralelo: 9
130+
----
131+
132+
. Ao realizar uma operação de reduce não há problema caso o acumulador seja associativo.
133+
+
134+
[source,java,indent=0]
135+
.{java-package}/parallelstreams/Streams_ParallelReduceAssociative.java
136+
----
137+
include::{section-java-package}/parallelstreams/Streams_ParallelReduceAssociative.java[tag=code]
138+
----
139+
+
140+
.Saída no console
141+
[source,console]
142+
----
143+
13440
144+
13440
145+
----
146+
+
147+
Perceba que o resultado com o Stream sequencial é idêntico ao paralelo. Isso ocorre porque a operação de multiplicação é associativa, ou seja, fazer `(2 x 2) x (3 x 3)` é o mesmo que fazer `(2 x 2 x 3) x 3`, ou até mesmo `2 x (2 x 3) x 3`.
148+
149+
. Ao realizar uma operação de reduce acumuladores não-associativos irá gerar resultados inesperados.
150+
+
151+
[source,java,indent=0]
152+
.{java-package}/parallelstreams/Streams_ParallelReduceNonAssociative.java
153+
----
154+
include::{section-java-package}/parallelstreams/Streams_ParallelReduceNonAssociative.java[tag=code]
155+
----
156+
+
157+
.Saída no console
158+
[source,console]
159+
----
160+
-18
161+
8
162+
----
163+
+
164+
Isso ocorre pois a operação de subtração não é associativa, então o resultado pode variar conforme o _Stream_ for "fatiado" para ser executado em paralelo. Ou seja, fazer `1 - 2 - 3 - 4` não é o mesmo que fazer `(1 - 2) - (3 - 4)`.
165+
166+
. Para coletar o resultado de um _Stream_ paralelo em um mapa, utilize a operação `toConcurrentMap`.
167+
+
168+
[source,java,indent=0]
169+
.{java-package}/parallelstreams/Streams_ParallelToConcurrentMap.java
170+
----
171+
include::{section-java-package}/parallelstreams/Streams_ParallelToConcurrentMap.java[tag=code]
172+
----
173+
+
174+
.Saída no console
175+
[source,console]
176+
----
177+
toMap: {Roseany=7, Amélia=6, Rodrigo=7, Rinaldo=7, Luiz=4}
178+
toConcurrentMap: {Amélia=6, Roseany=7, Rodrigo=7, Rinaldo=7, Luiz=4}
179+
----
180+
+
181+
Perceba que o resultados das operações pode ser diferente. Ao utilizar o _Collector_ `toConcurrentMap` em um _Stream_ paralelo, as operações podem ser executadas em qualquer ordem e não há necessidade de criar múltiplos `Map's` para serem combinados posteriormente. Em grandes _Streams_, isso pode ocasionar em um ganho de performance.
182+
183+
. Para coletar o resultado de um Stream paralelo utilize _groupingByConcurrent_ ao invés de _groupingBy_.
184+
+
185+
[source,java,indent=0]
186+
.{java-package}/parallelstreams/Streams_ParallelGroupingByConcurrent.java
187+
----
188+
include::{section-java-package}/parallelstreams/Streams_ParallelGroupingByConcurrent.java[tag=code]
189+
----
190+
+
191+
.Saída no console
192+
[source,console]
193+
----
194+
{4=[Luiz], 6=[Amélia], 7=[Rinaldo, Rodrigo, Roseany]}
195+
{4=[Luiz], 6=[Amélia], 7=[Roseany, Rodrigo, Rinaldo]}
196+
----
197+
+
198+
Pelo mesmo motivo do exemplo anterior, a ordem pode variar ao utilizar o `groupingByConcurrent`, porém pode haver ganho de performance em grandes _Streams_ paralelos, pois a ordem não é necessariamente seguida e não há necessidade de criar múltiplos mapas.
101199

102200
.Referências
103201
****
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.j6toj8.streams.parallelstreams;
2+
3+
import java.util.Optional;
4+
import java.util.stream.Stream;
5+
6+
public class Streams_ParallelFindAny {
7+
8+
public static void main(String[] args) {
9+
// tag::code[]
10+
Optional<Integer> findAny1 = Stream.of(7, 2, 1, 8, 4, 9, 2, 8)
11+
.findFirst();
12+
System.out.println("findAny Sequencial: " + findAny1.get());
13+
14+
Optional<Integer> findAny2 = Stream.of(7, 2, 1, 8, 4, 9, 2, 8)
15+
.parallel()
16+
.findAny();
17+
System.out.println("findAny Paralelo: " + findAny2.get());
18+
// end::code[]
19+
}
20+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.j6toj8.streams.parallelstreams;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import java.util.stream.Collectors;
6+
import java.util.stream.Stream;
7+
8+
public class Streams_ParallelGroupingByConcurrent {
9+
10+
public static void main(String[] args) {
11+
// tag::code[]
12+
Map<Object, List<String>> collect1 = Stream.of("Rinaldo", "Rodrigo", "Luiz", "Amélia", "Roseany")
13+
.parallel()
14+
.collect(Collectors.groupingBy(s -> s.length()));
15+
System.out.println(collect1);
16+
17+
Map<Object, List<String>> collect2 = Stream.of("Rinaldo", "Rodrigo", "Luiz", "Amélia", "Roseany")
18+
.parallel()
19+
.collect(Collectors.groupingByConcurrent(s -> s.length()));
20+
System.out.println(collect2);
21+
// end::code[]
22+
}
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.j6toj8.streams.parallelstreams;
2+
3+
import java.util.stream.Stream;
4+
5+
public class Streams_ParallelReduceAssociative {
6+
7+
public static void main(String[] args) {
8+
// tag::code[]
9+
Stream.of(7, 2, 3, 8, 2, 1, 4, 5)
10+
.reduce((e1, e2) -> e1 * e2)
11+
.ifPresent(System.out::println);
12+
13+
Stream.of(7, 2, 3, 8, 2, 1, 4, 5)
14+
.parallel()
15+
.reduce((e1, e2) -> e1 * e2)
16+
.ifPresent(System.out::println);
17+
// end::code[]
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.j6toj8.streams.parallelstreams;
2+
3+
import java.util.stream.Stream;
4+
5+
public class Streams_ParallelReduceNonAssociative {
6+
7+
public static void main(String[] args) {
8+
// tag::code[]
9+
Stream.of(7, 2, 3, 8, 2, 1, 4, 5)
10+
.reduce((e1, e2) -> e1 - e2)
11+
.ifPresent(System.out::println);
12+
13+
Stream.of(7, 2, 3, 8, 2, 1, 4, 5)
14+
.parallel()
15+
.reduce((e1, e2) -> e1 - e2)
16+
.ifPresent(System.out::println);
17+
// end::code[]
18+
}
19+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.j6toj8.streams.parallelstreams;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.Collections;
6+
import java.util.List;
7+
8+
public class Streams_ParallelStatefulOperation {
9+
10+
public static void main(String[] args) {
11+
// tag::code[]
12+
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
13+
List<String> list = Arrays.asList("A", "B", "C");
14+
15+
System.out.println("Ordem no forEachOrdered: ");
16+
list.parallelStream()
17+
.map(s -> {synchronizedList.add(s); return s;}) // operação com efeito colateral - altera o estado de um objeto
18+
.forEachOrdered(System.out::println);
19+
20+
System.out.println("Ordem na synchronizedList: ");
21+
for (String s : synchronizedList) {
22+
System.out.println(s);
23+
}
24+
// end::code[]
25+
}
26+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.j6toj8.streams.parallelstreams;
2+
3+
import java.util.Map;
4+
import java.util.stream.Collectors;
5+
import java.util.stream.Stream;
6+
7+
public class Streams_ParallelToConcurrentMap {
8+
9+
public static void main(String[] args) {
10+
// tag::code[]
11+
Map<String, Integer> collect1 = Stream.of("Rinaldo", "Rodrigo", "Luiz", "Amélia", "Roseany")
12+
.parallel()
13+
.collect(Collectors.toMap(s -> s, s -> s.length()));
14+
System.out.println("toMap: " + collect1);
15+
16+
Map<String, Integer> collect2 = Stream.of("Rinaldo", "Rodrigo", "Luiz", "Amélia", "Roseany")
17+
.parallel()
18+
.collect(Collectors.toConcurrentMap(s -> s, s -> s.length()));
19+
System.out.println("toConcurrentMap: " + collect2);
20+
// end::code[]
21+
}
22+
}

0 commit comments

Comments
 (0)