1 CONFIDENTIAL
Concurrency Utilities in
Java 8
04.07.2015
2 CONFIDENTIAL
Agenda
• Parallel Tasks
• Parallel Streams
• Parallel Array operations
3 CONFIDENTIAL
Agenda
• CompletableFuture
• ConcurrentHashMap
• Scalable Updatable Variables
• StampedLock
4 CONFIDENTIAL
Parallel Tasks
5 CONFIDENTIAL
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 CONFIDENTIAL
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 CONFIDENTIAL
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 CONFIDENTIAL
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);
}}}
9 CONFIDENTIAL
Parallel Streams
10 CONFIDENTIAL
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
11 CONFIDENTIAL
Parallel Streams
Stream.of(1,2,3,4,5,6,7,8,9).parallel().forEach(System.out::println);
Stream.of( new Integer[]{1,2,3,4,5,6,7,8,9} ).parallel().count();
LinkedList<Integer> list = new LinkedList<Integer>();
list.add(100); list.add(200); list.add(300);
Stream<Integer> stream1 = list.stream().parallel();
Stream<Integer> stream2 = list.parallelStream();
int[] values = IntStream.range(1000, 2000).parallel().toArray();
Arrays.stream(new double[]{1.1, 2.2, 3.3}).parallel().sum();
12 CONFIDENTIAL
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 CONFIDENTIAL
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 CONFIDENTIAL
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 CONFIDENTIAL
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 CONFIDENTIAL
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 CONFIDENTIAL
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 CONFIDENTIAL
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 CONFIDENTIAL
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 CONFIDENTIAL
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 …
21 CONFIDENTIAL
Parallel Streams
• Possible libraries you can use to measure performance of parallel streams include:
– JMH
– Caliper
– JUnitBenchmarks
22 CONFIDENTIAL
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.”
23 CONFIDENTIAL
Parallel Streams
(demo)
24 CONFIDENTIAL
Parallel Array Operations
25 CONFIDENTIAL
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
26 CONFIDENTIAL
Parallel Streams
Integer[] array = { 16, 7, 26, 14, 77 };
Arrays.parallelSort(array); [7, 14, 16, 26, 77]
Integer[] array = { 16, 7, 26, 14, 77 };
Arrays.parallelPrefix(array, (a, b) -> a * b); [16, 112, 2912, 40768, 3139136]
Integer[] array = new Integer[7];
Arrays.parallelSetAll(array, i -> i); [0, 1, 2, 3, 4, 5, 6]
27 CONFIDENTIAL
CompletableFuture
28 CONFIDENTIAL
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 4 triggers triggers triggers
29 CONFIDENTIAL
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
30 CONFIDENTIAL
CompletableFuture
CompletableFuture<Integer> task1 = new
CompletableFuture<Integer>();
// forcing completing of future by specifying result
task1.complete(10);
31 CONFIDENTIAL
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());
32 CONFIDENTIAL
CompletableFuture
CompletableFuture<Object> prev = null;
Supplier<Object> supplier = () -> { … };
for (int i = 0; i < count; i++) {
CompletableFuture<Object> task;
if (prev != null) {
task = prev.thenCompose((x) -> {
return CompletableFuture.supplyAsync(supplier);});
} else {
task = CompletableFuture.supplyAsync(supplier);
}
prev = task;
}
prev.get();
33 CONFIDENTIAL
ConcurrentHashMap
34 CONFIDENTIAL
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
35 CONFIDENTIAL
Scalable Updatable Variables
36 CONFIDENTIAL
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 CONFIDENTIAL
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
38 CONFIDENTIAL
StampedLock
39 CONFIDENTIAL
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
40 CONFIDENTIAL
StampedLock
• Example
StampedLock sl = new StampedLock();
long stamp = sl.writeLock();
try {
// do something that needs exclusive locking
} finally {
sl.unlockWrite(stamp);
}
41 CONFIDENTIAL
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;
}
42 CONFIDENTIAL
Thank you
43 CONFIDENTIAL
Resources
Concurrency Utility Enhancements in Java 8
https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/changes8.html
JVM concurrency: Java 8 concurrency basics
http://www.ibm.com/developerworks/library/j-jvmc2/index.html
Everything about Java 8
http://www.techempower.com/blog/2013/03/26/everything-about-java-8/
Java Specialists newsletter
http://www.javaspecialists.eu/archive/archive.jsp
44 CONFIDENTIAL
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 CONFIDENTIAL
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 CONFIDENTIAL
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/