This document discusses concurrency utilities introduced in Java 8 including parallel tasks, parallel streams, parallel array operations, CompletableFuture, ConcurrentHashMap, scalable updatable variables, and StampedLock. It provides examples and discusses when and how to properly use these utilities as well as potential performance issues. Resources for further reading on Java concurrency are also listed.
5. 5CONFIDENTIAL
Parallel Tasks
• Parallel tasks are represented by implementations of the
ForkJoinTask that are submitted to a ForkJoinPool instance for
execution
• Provides a direct way to implement a divide-and-conquer
mechanism for executing multiple tasks in parallel (in regard to
executing many independent tasks with the ExecutorService)
6. 6CONFIDENTIAL
Parallel Tasks
• Typical implementations of parallel tasks do not extend
directly ForkJoinTask
• RecursiveTask instances can be used to execute parallel tasks
that return a result
• RecursiveAction instances can be used to execute parallel
tasks that do not return a result
7. 7CONFIDENTIAL
Parallel Tasks
• ForkJoinPool.commonPool() - introduced in Java 8 in order to
retrieve the common pool that is basically a fork-join pool for
all ForkJoinTasks that are not submitted to a pool
• The common pool is used to implement parallel streams and
parallel array operations
• The common pool is as easy way to retrieve a ForkJoinPool for
parrallel operations that also improves resource utilization
8. 8CONFIDENTIAL
Parallel Tasks
public class NumberFormatAction extends RecursiveAction {
…
@Override
protected void compute() {
if (end - start <= 2) {
System.out.println(Thread.currentThread().getName() + " " + start + " " +
end);
} else {
int mid = start + (end - start) / 2;
NumberFormatAction left = new NumberFormatAction(start, mid);
NumberFormatAction right = new NumberFormatAction(mid + 1, end);
invokeAll(left, right);
}
public static void main(String[] args) {
NumberFormatAction action = new NumberFormatAction(1, 50);
ForkJoinPool.commonPool().invoke(action);
}}}
10. 10CONFIDENTIAL
Parallel Streams
• Streams (sequential and parallel) are introduced in java 8 with
the utilities in the java.util.stream package
• You can get a parallel stream from a collection using the
parallelStream() method or convert a sequential stream to a
parallel one using the parallel() method
12. 12CONFIDENTIAL
Parallel Streams
• However …
– Be careful when using parallel streams - they may be slower then
sequential streams
– Always do proper benchmarks (e.g. using JMH)
13. 13CONFIDENTIAL
Parallel Streams
• Parallel streams may not be proper when:
– A data structure that cannot be split well is used (e.g. LinkedList)
– Tasks are not independent and they cannot be split independently
– There are a lot of IO operations (file, network etc. ) or synchronization
– Simple operations are performed that may be optimized when using a
sequential stream
14. 14CONFIDENTIAL
Parallel Streams
• Parallel streams may not be proper when:
– A data structure that cannot be split well is used (e.g. LinkedList)
– Tasks are not independent and they cannot be split independently
– There are a lot of IO operations (file, network etc. ) or synchronization
– Simple operations are performed that may be optimized when using a
sequential stream
(Some of the limitations are implied due to the shared common Fork/Join pool being used for parallel streams)
15. 15CONFIDENTIAL
Parallel Streams
• Imagine a JavaEE application creating a parallel stream …
• … and several other applications creating parallel streams
• ALL of them use the common Fork/Join pool by default …
=> Simply do not create a parallel stream in a JavaEE application
16. 16CONFIDENTIAL
Parallel Streams
public static void listStream() {
List<Integer> numbers =
getRandomNumbers(10_000_000);
long start = System.nanoTime();
numbers.stream().
filter(num -> num%2 == 0).count();
long end = System.nanoTime();
System.out.println((end – start)/1000);
}
public static void listParallelStream() {
List<Integer> numbers =
getRandomNumbers(10_000_000);
long start = System.nanoTime();
numbers.parallelStream().
filter(num -> num%2 == 0).count();
long end = System.nanoTime();
System.out.println((end – start)/1000);
}
VS
17. 17CONFIDENTIAL
Parallel Streams
public static void listStream() {
List<Integer> numbers =
getRandomNumbers(10_000_000);
long start = System.nanoTime();
numbers.stream().
filter(num -> num%2 == 0).count();
long end = System.nanoTime();
System.out.println((end – start)/1000);
}
public static void listParallelStream() {
List<Integer> numbers =
getRandomNumbers(10_000_000);
long start = System.nanoTime();
numbers.parallelStream().
filter(num -> num%2 == 0).count();
long end = System.nanoTime();
System.out.println((end – start)/1000);
}
VS
List is a LinkedList instance
64 ms 309 ms
18. 18CONFIDENTIAL
Parallel Streams
public static void listStream() {
List<Integer> numbers =
getRandomNumbers(10_000_000);
long start = System.nanoTime();
numbers.stream().
filter(num -> num%2 == 0).count();
long end = System.nanoTime();
System.out.println((end – start)/1000);
}
public static void listParallelStream() {
List<Integer> numbers =
getRandomNumbers(10_000_000);
long start = System.nanoTime();
numbers.parallelStream().
filter(num -> num%2 == 0).count();
long end = System.nanoTime();
System.out.println((end – start)/1000);
}
VS
List is an ArrayList instance
55 ms 280 ms
19. 19CONFIDENTIAL
Parallel Streams
public static void listStream() {
List<Integer> numbers =
getRandomNumbers(10_000_000);
long start = System.nanoTime();
numbers.stream().
filter(num -> num%2 == 0).count();
long end = System.nanoTime();
System.out.println((end – start)/1000);
}
public static void listParallelStream() {
List<Integer> numbers =
getRandomNumbers(10_000_000);
long start = System.nanoTime();
numbers.parallelStream().
filter(num -> num%2 == 0).count();
long end = System.nanoTime();
System.out.println((end – start)/1000);
}
VS
(second run for both methods)
6 ms 1 ms
20. 20CONFIDENTIAL
Parallel Streams
• System.nanoTime() is the one of the most naïve (and incorrect)
approaches for doing benchmarks in practices …
• It does not take into account:
– a warm up phase before timing (triggers all initializations and JIT
compilations)
– side work done by the JVM (such as GC, output from another threads …)
– many other things depending on the type of performance statistics being
gathered …
22. 22CONFIDENTIAL
Parallel Streams
• The limitations imposed by the Fork/Join framework used by
parallel streams leads to a rigid critique:
“The JDK1.8 engineers are using the recursively decomposing F/J
framework, with its very, very narrow measure, as the engine for
parallel programming support simply because they don’t have anything
else.”
25. 25CONFIDENTIAL
Parallel Array Operations
• JDK 8 introduces a number of new methods in the Arrays utility
that allow parallel manipulation of arrays
• The methods are divided in three categories:
– parallelSort() – sorts an array in parallel
– parallelPrefix() – performs a cumulative operations on an array in parallel
– parallelSetAll() – sets all of the elements in an array in parallel
28. 28CONFIDENTIAL
CompletableFuture
• Provides a facility to create a chain of dependent non-blocking tasks
- an asynchronous task can be triggered as the result of a completion
of another task
• A CompletableFuture may be completed/cancelled by a thread
prematurely
• Such a facility is already provided by Google's Guava library
Task 1 Task 2 Task 3 Task 4triggers triggers triggers
29. 29CONFIDENTIAL
CompletableFuture
• Provides a very flexible API that allows additionally to:
o combine the result of multiple tasks in a CompletableFuture
o provide synchronous/asynchronous callbacks upon completion of a
task
o provide a CompletableFuture that executes when first task in group
completes
o provide a CompletableFuture that executes when all tasks in a group
complete
31. 31CONFIDENTIAL
CompletableFuture
CompletableFuture<Integer> task1 = CompletableFuture
.supplyAsync(() -> { … return 10;});
// executed on completion of the future
task1.thenApply((x) -> {…});
// executed in case of exception or completion of the
future
task1.handle((x, y) -> {…});
// can be completed prematurely with a result
// task1.complete(20);
System.err.println(task1.get());
34. 34CONFIDENTIAL
ConcurrentHashMap
• ConcurrentHashMap<V, K> class completely rewritten in order
to improve its usage as a cache and several new methods have
been added as part of the new stream and lambda
expressions:
o forEach
o forEachKey
o forEachValue
o forEachEntry
36. 36CONFIDENTIAL
Scalable Updatable Variables
• Maintaining a single variable that is updatable from many threads is
a common scalability issue
• Atomic variables already present in the JDK serve as a means to
implement updatable variables in a multithreaded environment
• New classes are introduced in order to reduce atomicity guarantees
in favor of high throughput - DoubleAccumulator, DoubleAdder,
LongAccumulator, LongAdder
37. 37CONFIDENTIAL
DoubleAccumulator accumulator = new DoubleAccumulator((x, y) -> x + y, 0.0);
// code being executed from some threads
accumulator.accumulate(10);
accumulator.accumulate(20);
System.out.println(accumulator.doubleValue());
Scalable Updatable Variables
39. 39CONFIDENTIAL
StampedLock
• A very specialized type of explicit lock
• Similar to ReentrantReadWriteLock but provides additionally
conversion between the lock modes (writing, reading and optimistic
reading)
• Optimistic read lock is a "cheap" version of a read lock that can be
invalidated by a read lock
• Lock state is determined by version and lock mode
41. 41CONFIDENTIAL
StampedLock
• Example
public long getValue() {
long stamp = sl.tryOptimisticRead();
long value = this.value;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
value = this.value;
} finally {
sl.unlockRead(stamp);
}
}
return value;
}
44. 44CONFIDENTIAL
Resources
Java Tutorials: Parallelism
https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html
Think twice before using Java 8 parallel streams
http://java.dzone.com/articles/think-twice-using-java-8
Parallel operations in Java 8
http://www.drdobbs.com/jvm/parallel-array-operations-in-java-8/240166287
Package java.util.stream
https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
How do I write a correct microbenchmark in Java
http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java
45. 45CONFIDENTIAL
Resources
Fork/Join Framework Tutorial: ForkJoinPool Example
http://howtodoinjava.com/2014/05/27/forkjoin-framework-tutorial-forkjoinpool-example/
Java 8 tutorial: Streams by Examples
http://howtodoinjava.com/2014/04/13/java-8-tutorial-streams-by-examples/
When to use parallel streams
http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html
A Java Fork/Join Calamity
http://www.coopsoft.com/ar/CalamityArticle.html
A Java Parallel Calamity
http://www.coopsoft.com/ar/Calamity2Article.html
46. 46CONFIDENTIAL
Resources
Java Parallel Stream Performance
http://stackoverflow.com/questions/22999188/java-parallel-stream-performance
Java Theory and practice: Anatomy of a flawed microbenchmark
https://www.ibm.com/developerworks/java/library/j-jtp02225/
Java 8 Concurrency Tutorial: Synchronization and Locks
http://winterbe.com/posts/2015/04/30/java8-concurrency-tutorial-synchronized-locks-examples/