Tuesday, September 26, 2023

What is LongAdder, AtomicLong, and LongAccumulator in Java? How it works? Example Tutorial

Hello guys, Concurrency in Java can be a challenging aspect of software development. As applications become more multithreaded and concurrent, Java developers often face issues related to thread safety, contention, and performance bottlenecks. To address these concerns, Java provides various tools and classes, one of which is LongAdder. In this article, we will explore what LongAdder is, when to use it, how to use it, and some best practices. You will be surprised to see how useful this little unknown class can be when multiple threads need to atomically update a shared counter like running count. 

What is LongAdder in Java? How it works?

LongAdder is a class introduced in Java 8 as part of the java.util.concurrent package. It's designed to efficiently maintain a set of long values that can be updated concurrently by multiple threads without contention

It's particularly useful for scenarios where multiple threads frequently update a shared counter or accumulator.

Unlike traditional atomic variables like AtomicLong, which can suffer from contention when multiple threads try to update them concurrently, LongAdder employs a more scalable strategy to reduce contention and improve performance.

It internally splits the long value into multiple cells, and each thread operates on a separate cell. 
Later, when you need to retrieve the sum of all the values, it combines them efficiently.

How LongAdder or DoubleAdder works



When to Use LongAdder in Java?

Here are common scenarios where using LongAdder make sense in Java:

1. High Contention on Shared Counters
If your application has multiple threads frequently updating a shared counter or accumulator, and you observe performance bottlenecks due to contention, LongAdder can provide a performance improvement.

2. Counting Occurrences
It's useful when you need to count occurrences of events or operations across threads without worrying about synchronization.

3. Statistics and Metrics
LongAdder class is a valuable tool for collecting statistics or metrics that are updated concurrently by multiple threads, such as tracking the number of requests, errors, or resource utilization.

How to Use LongAdder in Java?

Now that you know what is LongAdder class in JAva and when to use it, its time for some action and learn how to use it in your program. Using LongAdder is relatively straightforward.

Here are the basic steps:

1. Create a LongAdder instance:

LongAdder counter = new LongAdder();

2. Update the value
To increment the value by one, you can use increment() method as shown below:

counter.increment();

and to add a specific value, you can use add(long x):

counter.add(5);

3. Retrieve the sum
When you need to retrieve the sum of all the values updated by multiple threads, you can use the sum() method:

long total = counter.sum();

So, you can see that how easy it is use to use a LongAdder varabiel, its just a three step process to create, update and retrieve value. 

LongAdder Example in Java



Java LongAdder Example

In my opinion nothing is complete until you see a full running example, so, here is a complete Java program showing how to use LongAdder class in Java

import java.util.concurrent.*;

public class LongAdderExample {
    public static void main(String[] args) throws InterruptedException {
        LongAdder counter = new LongAdder();
        int numThreads = 4;
        CountDownLatch latch = new CountDownLatch(numThreads);

        for (int i = 0; i < numThreads; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    counter.increment();
                }
                latch.countDown();
            }).start();
        }

        latch.await(); // Wait for all threads to finish

        long total = counter.sum();
        System.out.println("Total count: " + total);
    }
}

Output:
Total count: 40000

In this example, four threads increment the LongAdder by 10,000 each, and the final total count is printed. The use of LongAdder ensures efficient concurrent updates without contention.

Best Practices while using LongAdder in Java

Here are some best practices when working with LongAdder:

1. Use it in High-Contention Scenarios
LongAdder shines in scenarios with high contention. For low-contention situations, a simple long variable or AtomicLong may be more efficient.

2. Consider Thread Safety
While LongAdder provides thread safety for updates, you should still ensure that your application logic doesn't introduce race conditions or other concurrency issues.

3. Measure Performance
Benchmark your application with and without LongAdder to ensure it provides the expected performance benefits in your specific use case.

4. Use the Right Data Structure
Depending on your use case, you may also want to explore other classes in the java.util.concurrent package, such as AtomicLong, AtomicInteger, or ConcurrentHashMap, to find the most suitable data structure for your needs.


Difference between LongAdder and AtomicLong in Java

While LongAdder and AtomicLong are both classes in Java that provide a way to perform atomic operations on long values, but they have different use cases and characteristics, which is also commonly asked in Java interviews. 

1. Performance and Contention

LongAdder is designed for scenarios with high contention, where multiple threads are frequently trying to update the same counter. It reduces contention by dividing the counter into multiple cells, each maintaining its own value, and then aggregating the results when needed. 

This makes it highly scalable under heavy contention because threads update different cells, reducing the need for synchronization.

AtomicLong, on the other hand, uses a single long value and relies on low-level CAS (Compare-And-Swap) operations for atomic updates. In high-contention scenarios, AtomicLong may experience performance degradation due to contention among threads contending for updates.

2. Memory Overhead

LongAdder typically has higher memory overhead compared to AtomicLong. This is because LongAdder maintains an array of cells to distribute updates, and additional memory is required to manage these cells.

AtomicLong has lower memory overhead because it only maintains a single long value.

3. Usage Scenarios

LongAdder is well-suited for scenarios where there are many threads updating a counter concurrently, and performance under contention is critical. Examples include metrics gathering, where multiple threads are incrementing counters.

On the other hand, AtomicLong is suitable for cases where there is low contention, and you want a simple, atomic long value. It's often used for tasks like maintaining a global counter in a single-threaded or low-concurrency environment.


Difference between LongAdder and LongAccumulator in Java

Now that we have seen the difference between LongAdder and AtomicLong, its time to compare LongAdder with another related class called LongAccumulator in Java. While LongAdder and LongAccumulator are both classes in Java that provide a way to perform atomic operations on long values, but they serve different purposes and have distinct characteristics:

1. Purpose and Usage

As I said before, LongAdder is primarily used for maintaining running sums or counts in scenarios with high contention, where multiple threads frequently update the same counter. 

It's optimized for addition operations and is designed to reduce contention by dividing the counter into multiple cells, each maintaining its own value, and then aggregating the results when needed. This makes it highly scalable in situations with heavy contention.

LongAccumulator, on the other hand, is more flexible and can perform a wider range of accumulation operations, not limited to addition. You can define a custom accumulation function when creating a LongAccumulator. It's suitable for more complex accumulation operations beyond simple addition.

2. Accumulation Function

LongAdder only supports addition operations (add and increment). It always accumulates values using addition.

LongAccumulator allows you to define a custom accumulation function when you create an instance. This function determines how values are accumulated. For example, you can use it to perform addition, subtraction, multiplication, or any other user-defined operation.

3. Example Usage

LongAdder is commonly used for scenarios where you need to maintain running sums or counts, such as metrics gathering, where multiple threads increment counters concurrently.

LongAccumulator is used when you have specific accumulation requirements beyond simple addition. For instance, you might use it to compute a running product of values or maintain a minimum or maximum value across multiple threads.

Here's a simplified example of using LongAccumulator to compute the maximum value from a collection of long values:
import java.util.concurrent.atomic.LongAccumulator;
import java.util.function.LongBinaryOperator;

public class LongAccumulatorExample {
    public static void main(String[] args) {
        // Define a custom accumulation function to compute the maximum value
        LongBinaryOperator maxOperator = (x, y) -> Math.max(x, y);

        // Create a LongAccumulator with the custom accumulation function
        LongAccumulator accumulator = new LongAccumulator(maxOperator, Long.MIN_VALUE);

        // Simulate concurrent updates (in this example, we use a single thread)
        long[] values = {5, 8, 2, 10, 3};

        for (long value : values) {
            accumulator.accumulate(value);
        }

        // Get the result, which is the maximum value
        long maxValue = accumulator.get();
        System.out.println("Maximum value: " + maxValue);
    }
}

In this example, LongAccumulator is used to find the maximum value from an array of long values using a custom accumulation function (maxOperator).

In summary, choose LongAdder when you need to maintain running sums or counts in scenarios with high contention, and choose LongAccumulator when you require more flexibility in defining custom accumulation operations beyond simple addition.


LongAdder Quizzes

And, now let's see how much you learned in this article and how many questions you can answer about LongAdder and its usage in Java:

1. What is LongAdder in Java primarily used for?
a) Adding long values together
b) Accumulating long values concurrently
c) Performing bitwise operations on long values
d) Comparing long values atomically

2. Compared to traditional atomic variables like AtomicLong, why might LongAdder be preferred in high-contention scenarios?
a) It is more memory-efficient
b) It provides better type safety
c) It reduces contention and improves performance
d) It supports more atomic operations

3. Which method is used to increment a LongAdder by a specified value?
a) add(long delta)
b) increment()
c) update(long delta)
d) increase(long delta)

4. How does LongAdder internally handle concurrent updates?
a) It uses a single synchronized block for all updates.
b) It employs a split-cell approach to reduce contention.
c) It uses locks to serialize updates.
d) It relies on thread priorities to ensure proper sequencing.

5. When should you consider using LongAdder over a simple long variable for accumulating values?
a) When you need the lowest possible memory usage
b) When only one thread updates the value
c) When multiple threads frequently update the value with high contention
d) When you want to perform floating-point operations on the value

6. Which of the following is a correct way to retrieve the total sum of a LongAdder?
a) long total = adder.value();
b) long total = adder.get();
c) long total = adder.sum();
d) long total = adder.retrieve();

7. What is the benefit of using LongAdder when compared to manual synchronization with locks for accumulating values?
a) LongAdder provides better type safety.
b) LongAdder is easier to implement.
c) LongAdder is more memory-efficient.
d) LongAdder reduces contention and can offer better performance.

8. Which package in Java contains the LongAdder class?
a) java.util.concurrent.locks
b) java.util.concurrent.atomic
c) java.util.concurrent.collections
d) java.util.concurrent.sync

9. What does the compareAndSet method do in LongAdder?
a) Compares two LongAdder instances for equality.
b) Compares the current value of a LongAdder to an expected value and sets it to a new value if they match.
c) Compares two LongAdder instances and returns a boolean indicating if they are equal.
d) Compares the current value of a LongAdder to an expected value and returns the result of the comparison.

10. In a scenario where multiple threads increment a LongAdder concurrently, what does the sum() method return?
a) The count of threads that incremented the LongAdder.
b) The sum of all increments performed by all threads.
c) The count of increments performed by a specific thread.
d) The average value of increments performed by all threads.


Answers:
b) Accumulating long values concurrently
c) It reduces contention and improves performance
a) add(long delta)
b) It employs a split-cell approach to reduce contention.
c) When multiple threads frequently update the value with high contention
c) long total = adder.sum();
d) LongAdder reduces contention and can offer better performance.
b) java.util.concurrent.atomic
b) Compares the current value of a LongAdder to an expected value and sets it to a new value if they match.
b) The sum of all increments performed by all threads

That's all about how to use LongAdder class in Java. LongAdder is a valuable addition to Java's concurrency toolkit, designed to efficiently handle concurrent updates to long values with reduced contention. 

When used in the right situations, it can significantly improve the performance of your multithreaded Java applications.  Similar to LongAdder, Java also has DoubleAdder which can be used to efficiently update a double counter. 

However, like any concurrency tool, it should be used judiciously, and its impact on performance should be measured to ensure it aligns with your application's requirements.


Other Java Concurrency Articles you may like
  • The Complete Java Developer RoadMap (roadmap)
  • 5 Courses to Learn Java Multithreading in-depth (courses)
  • 10 Java Multithreading and Concurrency Best Practices (article)
  • Top 50 Multithreading and Concurrency Questions in Java (questions)
  • 10 Courses to learn Java in for Beginners (courses)
  • Difference between CyclicBarrier and CountDownLatch in Java? (answer)
  • How to avoid deadlock in Java? (answer)
  • Understanding the flow of data and code in Java program (answer)
  • Is Java Concurrency in Practice still valid (answer)
  • How to do inter-thread communication in Java using wait-notify? (answer)
  • 10 Tips to become a better Java Developer (tips)
  • 5 Essential Skills to Crack Java Interviews (skills)
  • How does Exchanger works in Java Multithreading (tutorial)
  • Top 5 Books to Master Concurrency in Java (books)

Thanks for reading this article so far. If you like this article then please share it with your friends and colleagues. If you have any questions or feedback then please drop a note.

1 comment :

Anonymous said...

Thank you for such a great, in-depth article, I was surprised to know how LongAdder improves performance and manage contention, this is nothing short of extraordinary design.

Post a Comment