LongAccumulator
Explore how the Java LongAccumulator class enables versatile accumulation with user-defined operations for multithreaded environments. Understand its advantages over LongAdder and AtomicLong by distributing contention and improving performance under high concurrency. Learn to implement and apply LongAccumulator effectively in your Java concurrency solutions.
We'll cover the following...
If you are interviewing, consider buying our number#1 course for Java Multithreading Interviews.
Overview
The LongAccumulator class is similar to the LongAdder class, except that the LongAccumulator class allows for a function to be supplied that contains the logic for computing results for accumulation. In contrast to LongAdder, we can perform a variety of mathematical operations rather than just addition. The supplied function to a LongAccumulator is of type LongBinaryOperator. The class LongAccumulator extends from the class Number but doesn’t define the methods compareTo(), equals(), or hashCode() and shouldn’t be used as keys in collections such as maps.
An example of creating an accumulator that simply adds long values presented to it
// function that will be supplied to an instance of LongAccumulator
LongBinaryOperator longBinaryOperator = new LongBinaryOperator() {
@Override
public long applyAsLong(long left, long right) {
return left + right;
}
};
// instantiating an instance of LongAccumulator with an initial value of zero
LongAccumulator longAccumulator = new LongAccumulator(longBinaryOperator, 0);
Note that in the above example, we have supplied a function that simply adds the new long value presented to it. The method applyAsLong has two operands left and right. The left operand is the current value of the LongAccumulator. In the above example, it’ll be zero initially, because that is what we are passing-in to the constructor of the LongAccumulator instance. The code widget below runs this example and prints the operands and the final sum.
As you can see we aren’t confined to adding long values, rather we can perform as complex operations as desired in the supplied function, which makes the LongAccumulator class far more versatile than the LongAdder class which is limited to addition. In fact, LongAdder can be thought of as a specialized case of LongAccumulator for keeping counts and sums.
Distributing contention
We can achieve the same functionality by using an instance of AtomicLong as we can with the LongAccumulator, however, the rational for LongAccumulator is to distribute contention among threads by maintaining a set of variables that grow dynamically and each one is updated by only a subset of threads. Thus the contention is spread from a single variable to several variables. When the current value is asked for by invoking the get() or the longValue() methods, all the underlying variables are accumulated by applying the supplied function and the result is returned. The expected throughput of LongAccumulator is significantly higher when used in place of AtomicLong under high contention. The improved performance comes at the cost of using more space.
Order of accumulation
When multiple threads accumulate an instance of LongAccumulator, eventually all the long values in the underlying set are accumulated using the supplied function. The order in which these long values are accumulated isn’t guaranteed and the supplied function should produce the same value irrespective of the order in which these values are accumulated. In case, the supplied function isn’t commutative i.e., left + right isn’t the same as right + left then the accumulation can produce different results for the same series of accumulated long values.
Example
In the example below, we use the LongAccumulator class to keep track of the maximum value observed. There are several threads that use the ThreadLocalRandom class to produce a random long value less than 1000, and then attempt to update the instance of LongAccumulator. We conduct the same test using AtomicLong and time the two tests. Go through the listing which is self-explanatory.
Note that the above test is crude and imprecise in nature, but does give a general idea of the performance of the two classes under high contention. We can tweak the different parameters such as the number of iterations or numThreads to produce an environment with different contention characteristics and maybe different results.