Java SE 8 For the Really Impatient, Note 9
1,660 words in 10 minutes
Chapter 6 Concurrency Enhancements
java.util.concurrent is a mix of useful utilities for the application programmer and power tools for library authors, without much effort to separate the two.
- Updating atomic variables has become simpler with the
DoubleAccumulatorare more efficient than
AtomicDoubleunder high contention.
- Updating entries in a
ConcurrentHashMaphas become simpler with the
ConcurrentHashMapnow has bulk operations
forEach, with variants operating on keys, values, keys and values, and entries.
- A set view lets you use a
Arraysclass has methods for parallel sorting, filling, and prefix operations.
- Completable futures let you compose asynchronous operations.
java.util.concurrent.atomic package provided classes for lock-free mutation of variables since Java 5. You can safely generate a sequence of numbers like below:
incrementAndGet: atomically increments the
AtomicLong and returns the post-increment value.
If you want to make a more complex update, you have to use the
compareAndSet method. Suppose you want to keep track of the largest value that is observed by different thread.
Instead, compute the new value and use
compareAndSet in a loop.
If another thread is also updating
largest, it is possible the it has beat this thread to it. Then
compareAndSet will return
false without setting the new value. The loop tries again. Eventually, it will succeed replacing the existing value with the new one. The
compareAndSet method maps to a processor operation that is faster than using a lock.
In Java 8, you can use a lambda expression.
accumulateAndGet method takes a binary operator that is used to combine the atomic value and the supplied argument.
getAndAccumulate the return the old value.
These methods are also provided for:
LongAccumulator can be used to solve the problem that a large number of threads accessing the same atomic values.
LongAdder: composed of multiple variables whose collective sum is the current value. Multiple threads can update different summands, and new summands are automatically provided when the number of threads increases. Efficient when the value of the sum is not needed until after all work has been done. If you anticipate high contention, you should simply use a
LongAdder instead of an
increment to increment a counter, or
add to add a quantity, and
sum to retrieve the total.
increment does not return the old value which would undo the efficiency gain.
LongAccumulator: generalizes the idea to an arbitrary accumulation operaiton. Provide the operation and its neutral element in the constructor. Call
accumulate to incorporate new values. Call
get to obtain the current value.
Internally, the accumulator has variables a1, a2, …, an. Each variable is initialized with the neutral element.
accumulate is called with value v, then one of them is atomically updated as ai = ai op v, where op is the accumulation operation written in infix form. In the above example, a call to
accumulate computes ai = ai + v for some i.
The result of
get is a1 op a2 op … op an.
If you choose a different operation, you can compute maximum or minimum.
The operation must be associative and commutative, meaning that the final result must be independent of the order.
DoubleAccumulator work in the same way with
StampedLock: can be used to implement optimistic reads. Not recommended to use locks.
tryOptimisticRead, upon which you get a “stamp”. Read your values and check whether the stamp is still valid(no other thread has obtained a write lock). If so, you can use the values. If not, get a read lock (which blocks any writers).