http://winterbe.com/posts/2015/05/22/java8-concurrency-tutorial-atomic-concurrent-map-examples/
The method
AtomicIntegerArray, AtomicLongArray
compare-and-swap operation
You get the value of the variable, which is the old value of the variable.
You change the value of the variable in a temporal variable, which is the new value of the variable.
You substitute the old value with the new value, if the old value is equal to the actual value of the variable. The old value may be different from the actual value if another thread has changed the value of the variable.
LongAccumulator/DoubleAccumulator are more efficient than AtomicLong/AtomicDouble under high contention.
ConcurrentHashMap
- search, reduce, forEach, compute and merge
public static AtomicLong largest = new AtomicLong();
largest.set(Math.max(largest.get(), observed)); // Error—race condition!
This update is not atomic. Instead, compute the new value and use compareAndSet in a loop:
do {
oldValue = largest.get();
newValue = Math.max(oldValue, observed); if(oldValue==newValue) break;
} while (!largest.compareAndSet(oldValue, newValue));
In Java 8, you don’t have to write the loop boilerplate any more.
largest.updateAndGet(x -> Math.max(x, observed));
largest.accumulateAndGet(observed, Math::max);
The accumulateAndGet method takes a binary operator that is used to combine the atomic value and the supplied argument.
A LongAdder is 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. This is efficient in the common situation where the value of the sum is not needed until after all work has been done
The LongAccumulator generalizes this idea to an arbitrary accumulation operation. In the constructor, you provide the operation, as well as its neutral element. To incorporate new values, call accumulate. Call get to obtain the current value.
LongAccumulator adder = new LongAccumulator(Long::sum, 0);
// In some thread...
adder.accumulate(value);
Internally, the accumulator has variables a1, a2, ..., an. Each variable is initialized with the neutral element (0 in our example).
When 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 our example, a call to accumulate computes ai = ai + v for some i.
The result of get is a1 op a2 op ... op an. In our example, that is the sum of the accumulators, a1 + a2 + ... + an.
If you choose a different operation, you can compute maximum or minimum (see Exercise 4). In general, the operation must be associative and commutative. That means that the final result must be independent of the order in which the intermediate values were combined.
map.putIfAbsent(word, new LongAdder()).increment();
map.compute(word, (k, v) -> v == null ? 1 : v + 1);
map.computeIfAbsent(word, k -> new LongAdder()).increment();
That is almost like the call to putIfAbsent that you saw before, but the LongAdder constructor is only called when a new counter is actually needed.
map.merge(word, 1L, (existingValue, newValue) -> existingValue + newValue);
map.merge(word, 1L, Long::sum);
If the function that is passed to compute or merge returns null, the existing entry is removed from the map.
Bulk Operations
String result = map.search(threshold, (k, v) -> v > 1000 ? k : null);
map.forEach(threshold,
(k, v) -> System.out.println(k + " -> " + v));
The second variant takes an additional transformer function, which is applied first, and its result is passed to the consumer:
map.forEach(threshold,
(k, v) -> k + " -> " + v, // Transformer
System.out::println); // Consumer
Long sum = map.reduceValues(threshold, Long::sum);
Integer maxlength = map.reduceKeys(threshold,
String::length, // Transformer
Integer::max); // Accumulator
Long count = map.reduceValues(threshold,
v -> v > 1000 ? 1L : null,
Long::sum);
Set<String> words = ConcurrentHashMap.<String>newKeySet();
The method
updateAndGet()
accepts a lambda expression in order to perform arbitrary arithmetic operations upon the integer:IntStream.range(0, 1000)
.forEach(i -> {
Runnable task = () ->
atomicInt.updateAndGet(n -> n + 2);
executor.submit(task);
});
The method accumulateAndGet()
accepts another kind of lambda expression of type IntBinaryOperator
. We use this method to sum up all values from 0 to 1000 concurrently in the next sample:
ConcurrentHashMap
The method
search()
accepts a BiFunction
returning a non-null search result for the current key-value pair or null
if the current iteration doesn't match the desired search criteria. As soon as a non-null result is returned further processing is suppressed. Keep in mind that ConcurrentHashMap
is unordered. The search function should not depend on the actual processing order of the map. If multiple entries of the map match the given search function the result may be non-deterministic.String result = map.search(1, (key, value) -> {
System.out.println(Thread.currentThread().getName());
if ("foo".equals(key)) {
return value;
}
return null;
});
String result = map.searchValues(1, value -> {
System.out.println(Thread.currentThread().getName());
if (value.length() > 3) {
return value;
}
return null;
});
The method reduce()
already known from Java 8 Streams accepts two lambda expressions of type BiFunction
. The first function transforms each key-value pair into a single value of any type. The second function combines all those transformed values into a single result, ignoring any possible null
values.String result = map.reduce(1,
(key, value) -> {
System.out.println("Transform: " + Thread.currentThread().getName());
return key + "=" + value;
},
(s1, s2) -> {
System.out.println("Reduce: " + Thread.currentThread().getName());
return s1 + ", " + s2;
});
Java SE 8 for the Really ImpatientAtomicIntegerArray, AtomicLongArray
compare-and-swap operation
You get the value of the variable, which is the old value of the variable.
You change the value of the variable in a temporal variable, which is the new value of the variable.
You substitute the old value with the new value, if the old value is equal to the actual value of the variable. The old value may be different from the actual value if another thread has changed the value of the variable.
LongAccumulator/DoubleAccumulator are more efficient than AtomicLong/AtomicDouble under high contention.
ConcurrentHashMap
- search, reduce, forEach, compute and merge
public static AtomicLong largest = new AtomicLong();
largest.set(Math.max(largest.get(), observed)); // Error—race condition!
This update is not atomic. Instead, compute the new value and use compareAndSet in a loop:
do {
oldValue = largest.get();
newValue = Math.max(oldValue, observed); if(oldValue==newValue) break;
} while (!largest.compareAndSet(oldValue, newValue));
In Java 8, you don’t have to write the loop boilerplate any more.
largest.updateAndGet(x -> Math.max(x, observed));
largest.accumulateAndGet(observed, Math::max);
The accumulateAndGet method takes a binary operator that is used to combine the atomic value and the supplied argument.
A LongAdder is 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. This is efficient in the common situation where the value of the sum is not needed until after all work has been done
The LongAccumulator generalizes this idea to an arbitrary accumulation operation. In the constructor, you provide the operation, as well as its neutral element. To incorporate new values, call accumulate. Call get to obtain the current value.
LongAccumulator adder = new LongAccumulator(Long::sum, 0);
// In some thread...
adder.accumulate(value);
Internally, the accumulator has variables a1, a2, ..., an. Each variable is initialized with the neutral element (0 in our example).
When 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 our example, a call to accumulate computes ai = ai + v for some i.
The result of get is a1 op a2 op ... op an. In our example, that is the sum of the accumulators, a1 + a2 + ... + an.
If you choose a different operation, you can compute maximum or minimum (see Exercise 4). In general, the operation must be associative and commutative. That means that the final result must be independent of the order in which the intermediate values were combined.
map.putIfAbsent(word, new LongAdder()).increment();
map.compute(word, (k, v) -> v == null ? 1 : v + 1);
map.computeIfAbsent(word, k -> new LongAdder()).increment();
That is almost like the call to putIfAbsent that you saw before, but the LongAdder constructor is only called when a new counter is actually needed.
map.merge(word, 1L, (existingValue, newValue) -> existingValue + newValue);
map.merge(word, 1L, Long::sum);
If the function that is passed to compute or merge returns null, the existing entry is removed from the map.
Bulk Operations
String result = map.search(threshold, (k, v) -> v > 1000 ? k : null);
map.forEach(threshold,
(k, v) -> System.out.println(k + " -> " + v));
The second variant takes an additional transformer function, which is applied first, and its result is passed to the consumer:
map.forEach(threshold,
(k, v) -> k + " -> " + v, // Transformer
System.out::println); // Consumer
Long sum = map.reduceValues(threshold, Long::sum);
Integer maxlength = map.reduceKeys(threshold,
String::length, // Transformer
Integer::max); // Accumulator
Long count = map.reduceValues(threshold,
v -> v > 1000 ? 1L : null,
Long::sum);
Set<String> words = ConcurrentHashMap.<String>newKeySet();