1. Introduction
Ensuring thread-safe operations on shared variables is crucial in modern Java applications, especially in multithreaded environments. The AtomicInteger class, part of the java.util.concurrent.atomic
package, provides a way to perform atomic (indivisible) operations on integers without explicit synchronization or locks. In this article, you’ll learn how to create and manipulate AtomicIntegers in your Java applications.
2. What is an AtomicInteger?
According to its Javadoc, an AtomicInteger represents an integer that can be updated atomically. In other words, an AtomicInteger can be used safely, in a multithreaded environment without explicit synchronization or locks. Common use cases for this data type are:
- Shared counters
- Shared state updates
- Non-blocking algorithms
3. Create an AtomicInteger
Java provides two constructors for the AtomicInteger class: the default no-argument constructor, and a second constructor that takes the initial value as argument.
AtomicInteger withoutInitialValue = new AtomicInteger();//Initial value is 0
AtomicInteger withInitialValue = new AtomicInteger(10);//Initial value is 10
4. Operations on AtomicIntegers
AtomicInteger is part of the java.util.concurrent.atomic
package. Depending on your specific use case, AtomicInteger provides a variety of methods. All the methods are atomic, meaning they are performed as one single operation.
4.1. getAndSet(int newValue) / get() / set()
These are among the most used methods of the AtomicInteger class. Use them to get and set the value of your shared object to a specific value.
AtomicInteger counter = new AtomicInteger(10);//counter = 10
int value = counter.get();//counter = 10, value=10
counter.set(15);//counter = 15, value=10
value = counter.get();//counter = 15, value=15
value = counter.getAndSet(20);//counter = 20, value=15
value = counter.get();//counter = 20, value=20
4.2. Increment and Decrement atomically
These methods are mostly used in the context of a shared counter.
AtomicInteger counter1 = new AtomicInteger(10);//counter1 = 10
AtomicInteger counter2 = new AtomicInteger(10);//counter2 = 10
AtomicInteger counter3 = new AtomicInteger(10);//counter3 = 10
AtomicInteger counter4 = new AtomicInteger(10);//counter4 = 10
int value1 = counter1.incrementAndGet();//counter1 = 11, value1=11
int value2 = counter2.getAndIncrement();//counter2 = 11, value2=10
int value3 = counter3.decrementAndGet();//counter3 = 9 value3=9
int value4 = counter4.getAndDecrement();//counter4 = 9, value4=10
4.3. addAndGet(int delta) / getAndAdd(int delta)
Unlike the previous methods, these methods allow you to add any value to the current value of the shared object.
AtomicInteger pageViewCount = new AtomicInteger(0);//pageViewCount = 0
int value = pageViewCount.getAndAdd(10);//pageViewCount = 10, value=0
value = pageViewCount.addAndGet(5);//pageViewCount = 15, value=15
4.4. compareAndSet(int expectedValue, int newValue)
This method will set the AtomicInteger value to newValue
if and only if the current value matches the expected value.
AtomicInteger counter = new AtomicInteger(10);//counter = 10
boolean check1 = counter.compareAndSet(5,20);//check1 = false, counter = 10
boolean check2 = counter.compareAndSet(10,20);//check2 = true, counter = 20
The method is ideal for ensuring that a different thread has not modified the counter before you update it
int currentStock = stock.get();
//Some code
if (stock.compareAndSet(currentStock, currentStock - 1)) {
System.out.println(Thread.currentThread().getName() + ": Stock updated successfully! Remaining stock: " + stock.get());
//More code
}else{
System.out.println("The stock was updated by a different thread");
//Adapt the code
}
4.5. accumulateAndGet(int x, IntBinaryOperator function)
This method is suitable if you want to update a shared value with a custom function.
AtomicInteger balance = new AtomicInteger(1000);
IntBinaryOperator function = (IntBinaryOperator) Integer::sum;
int newBalance = balance.accumulateAndGet(500,function);//balance = 1500, newBalance = 1500;
5. Real-world Application
As we said earlier in this tutorial, AtomicIntegers are ideal for scenarios like counters, shared state updates, and non-blocking algorithms. In the following example, we implement a counter that many threads update.
class CustomCounter implements Runnable{
private final AtomicInteger counter;
public CustomCounter(AtomicInteger counter){
this.counter = counter;
}
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
counter.incrementAndGet(); // Atomic increment
}
}
}
void realWorldApplication() throws InterruptedException {
AtomicInteger counter = new AtomicInteger(0);
// Create 20 threads to increment the counter
Thread[] threads = new Thread[20];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new CustomCounter(counter));
threads[i].start();
}
// Wait for all threads to finish
for (Thread thread : threads) {
thread.join();
}
System.out.println("Final Counter Value: " + counter.get());//20 000
}
6. Conclusion
In this tutorial, you learned about the AtomicInteger, class which is the recommended Java class if you want to achieve indivisible operations on integers without using explicit synchronization or locks.
You can find the complete code of this article here in GitHub.