I would like to have your input about the - somewhat broad - certification topic:
Use synchronized keyword and java.util.concurrent.atomic package to control the order of thread execution
Is my understanding that in Java 8 classes such as AtomicLong / AtomicInteger were rewritten replacing compare-and-swap techniques with fetch-and-add. Other than that, looks like some new methods were introduced to take advantage of lambda constructs.
I've also read about new classes in the java.util.concurrent.atomic such as LongAccumulator and LongAdder meant outperform AtomicLong under heavy update contention situations.
Do you know if those improvements are part of the exam? If so, could your briefly comment about how to use those new constructs and when to choose one over the other?
Thank you for your interest in our book and for asking a broad question on concurrency.
As you rightly said, older approach in Java for acquiring and releasing locks for primitive operations is not efficient, and in such cases, Java provides an efficient alternative in the form of atomic variables in the java.util.concurrent.atomic package. Of the classes in the java.util.concurrency.atomic subpackage, for me, AtomicInteger and AtomicLong are the most important. I haven't used LongAccumulator and LongAdder, so couldn't really comment anything on that (and I did not get any questions on these classes in the exam).
For me, like millions of other programmers, concurrency has been a slippery slope. There had been many times when I thought my code was just fine and did not have bugs only to promptly fail later. I remember one specific example: I got a review comment identifying a problem in using CylicBarrier in my code. I analyzed the code and ignored the review comment because the code just looked fine, executed as expected when I ran it multiple times, and I thought the reviewer made a mistake. A nasty surprise awaited me when it was reported as a bug a few months later! Hard lesson learnt: You can never be sure with concurrency (does not matter if it is older low-level locks or with new concurrency utilities).
Fortunately, parallel streams is very attractive for writing concurrent code. When I first tried parallel streams and saw the results, these words came to my mind: "this is awesome!!". Since it is high-level construct (all the constructs in java.util.concurrency.atomic and java.util.concurrency.locks package are still very low-level), and we can flip from serial to parallel stream, I find this approach to be very attractive. Of course, only later did I figure out these:
we can apply parallel streams only in relevant contexts
using parallel streams inappropriately can in fact slow-down performance
using parallel streams incorrectly can result in (sometimes subtle and nasty) bugs
To elaborate on the last point, to use parallel streams correctly, it is important not to depend on global state. In other words, the computations should be “side-effect” free. Here is an example from our book:
This program prints:
In this program, we are splitting the words in the string "the quick brown fox jumps over the lazy dog" and then combining it again. For combining the words, we are using a global variable result and modifying it by passing the StringConcatenator::concatStr() method reference in the forEach() method of the stream. Because the underlying stream is a sequential stream, we don’t seem to get into trouble and we were able to reconstruct the string correctly. However, here is a modified version of the program that converts the stream to a parallel stream by calling parallel() .
With this single change, we get garbled string! When we ran this program it printed:
When the stream is parallel, the task is split into multiple sub-tasks and different threads execute it. The calls to forEach(StringConcatenator::concatStr) now access the globally accessible variable result StringConcatenator class. Hence this program suffers from a race condition problem. How do we fix this problem? We need to get rid of modifying the global state and keep the reduction localized. I leave it to you to read the book if you want the coding example for that (see Listing 11-14. pp 347)!
Thank you for the prompt answer and the much welcome lesson on the perks of parallel streams.
If you don't mind a follow up question, besides AtomigLong, parallel streams and CyclicBarrier what classes / constructs are important to master when studying concurrency for the certification?
Locks, synchronized, ExecutorService? Any other hints about what is covered by the exam (should I be worried about things such as Semaphore and StampedLock)?
what classes / constructs are important to master when studying concurrency for the certification?
Executor, ExecutorService, concurrency collections (Phaser, CyclicBarrier, etc), CopyOnWriteArrayList, Callable, Future, Fork/join related constructs (such as ForkJoinPool, RecursiveTask, RecursiveAction)...
Since it is a vast range of classes and interfaces that we need to be comfortable with, the best approach is to solve problems (write code) using them. Of course reading relevant JavaDoc pages certainly helps.
Chapter 11 (Concurrency) of our book covers all these classes/interfaces to the necessary depth and breadth required, so request you to check it out. Given that this topic is vast, reading quick reference card before attending the exam will also help.