AtomicInteger is about having multiple threads update a counter
volatile is used on a field where one thread updates a value and another thread reads the value.
If however a thread needs to read the volatile value and then process it to give a new value which it then sets back to replace the old value then volatile cannot be used. In other words volatile is unsuitable for operations of the kind "x = x + 1" as using volatile by itself does not ensure atomicity of the read, increment and write. Infact with a volatile field a thread can interfere with the "x = x + 1" operation of another thread.
Therefore when you want to perform a "x = x + 1" operation it is best to either do it in a synchronized block or use a CAS atomic class such as AtomicInteger to update the value.
CAS stands for "Compare And Swap" and is a built on top of a hardware feature that allows operations such as "x = x + 1" to be atomic.
With CAS, the implementation of atomic "x = x + 1" style operations goes something like: A original value which is taken at "time one" just before performing some operation on it is compared against the live original value at "time two" just before replacing it with the new computed value. If the original value has changed during the duration of the operation then the "x = x + 1" style operation repeats itself as it assumes its original value has become stale.
If there is serious contention there can be a lot of repeat tries for a thread to perform a "x = x + 1" operation.
As the example is incrementing a counter we are using AtomicInteger instead of a volatile.
AtomicInteger can be faster than using a synchronized block. It is more light weight. If however you do not need perform a "x = x + 1" style operation then it is best to use a volatile.
To get the code for this example:
git clone https://github.com/spotadev/java-examples.git
In both src/main/java and src/test/java navigate to this package:
com.javaspeak.java_examples.concurrency.cas.atomicinteger
You can run the testng unit test using a testng plugin for your IDE or you can run the main method of:
AtomicIntegerCounterTest
See: testng
package com.javaspeak.java_examples.concurrency.cas.atomicinteger;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author John Dickerson - 5 Dec 2022
*/
public class AtomicIntegerCounter {
// The disadvantage of this approach is that there is a cost for wrapping
// a volatile int in a AtomicInteger. If you want to keep using a
// volatile int then you should use AtomicIntegerFieldUpdater instead
private AtomicInteger atomicInteger = new AtomicInteger( -1 );
/**
* @return counter value after it has been incremented by one
*/
public int incrementByOne() {
// This operation is atomic ( x = x + 1 )
return atomicInteger.addAndGet( 1 );
}
public static void main( String[] args ) {
AtomicIntegerCounter atomicIntegerCounter = new AtomicIntegerCounter();
// This call would ordinarily be called by another thread
System.out.println( atomicIntegerCounter.incrementByOne() );
}
}
package com.javaspeak.java_examples.concurrency.cas.atomicinteger;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.testng.Assert;
import org.testng.TestListenerAdapter;
import org.testng.TestNG;
import org.testng.annotations.Test;
/**
* @author John Dickerson - 5 Dec 2022
*/
public class AtomicIntegerCounterTest {
private AtomicIntegerCounterTestThread[] runThreads(
int numberThreads, int maxNumber )
throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch( numberThreads );
AtomicIntegerCounter atomicIntegerCounter = new AtomicIntegerCounter();
AtomicIntegerCounterTestThread[] threads =
new AtomicIntegerCounterTestThread[numberThreads];
for ( int i = 0; i < numberThreads; i++ ) {
threads[i] =
new AtomicIntegerCounterTestThread(
countDownLatch, atomicIntegerCounter, maxNumber );
}
for ( int i = 0; i < threads.length; i++ ) {
threads[i].start();
}
countDownLatch.await( 20, TimeUnit.SECONDS );
return threads;
}
/**
* Tests multiple threads incrementing the AtomicIntegerCounter counter at the same time.
* <p>
* Whenever a thread updates the counter it saves the value in ints own array at the index of
* the array which is the same as the value.
* <p>
* Initially the array is initialised with -1 in each index.
* <p>
* after running the array could look like:
* <p>
* 1 2 -1 4 5 -1 -1 7
* <p>
* After all the threads have finished running the values in the arrays of all threads are
* compared to make sure that no more than one thread had the same value in its array (ie there
* are no duplicates).
* <p>
* This was achieved by reading the values from the arrays and putting them in a TreeSet.
* Before adding an entry to the set it was checked whether there was a value in the set
* already. If there was the same value in the set already that means we have a duplicate and
* the test fails.
* <p>
* Finally the test checks whether there are any values that were skipped in the Set. if there
* are gaps the test fails.
*
* @throws InterruptedException
*/
@Test
public void testUpdateCounter() throws InterruptedException {
int numberThreads = 3;
int maxNumber = 1000;
// blocks until all threads complete
AtomicIntegerCounterTestThread[] testThreads = runThreads( numberThreads, maxNumber );
int[] numbers;
Set<Integer> numberSet = new TreeSet<Integer>();
for ( int i = 0; i < testThreads.length; i++ ) {
numbers = testThreads[i].getNumbers();
for ( int j = 0; j < numbers.length; j++ ) {
if ( numbers[j] != -1 ) {
if ( numberSet.contains( numbers[j] ) ) {
Assert.fail( "duplicate" );
}
else {
numberSet.add( numbers[j] );
}
}
}
}
int numberElements = numberSet.size();
Iterator<Integer> numberIterator = numberSet.iterator();
// check for gaps
for ( int i = 0; i < numberElements; i++ ) {
Assert.assertEquals( numberIterator.next(), Integer.valueOf( i ) );
}
}
public static void main( String[] args ) {
TestListenerAdapter tla = new TestListenerAdapter();
TestNG testng = new TestNG();
testng.setTestClasses( new Class[] { AtomicIntegerCounterTest.class } );
testng.addListener( tla );
testng.run();
}
}
package com.javaspeak.java_examples.concurrency.cas.atomicinteger;
import java.util.concurrent.CountDownLatch;
/**
* @author John Dickerson - 5 Dec 2022
*/
public class AtomicIntegerCounterTestThread extends Thread {
private int maxNumber;
private int[] numbers;
private CountDownLatch countDownLatch;
private AtomicIntegerCounter atomicIntegerCounter;
public AtomicIntegerCounterTestThread(
CountDownLatch countDownLatch,
AtomicIntegerCounter atomicIntegerCounter, int maxNumber ) {
this.countDownLatch = countDownLatch;
this.atomicIntegerCounter = atomicIntegerCounter;
this.maxNumber = maxNumber;
this.numbers = new int[maxNumber];
for ( int i = 0; i < numbers.length; i++ ) {
numbers[i] = -1;
}
}
public int[] getNumbers() {
return numbers;
}
@Override
public void run() {
int currentNumber;
while ( true ) {
currentNumber = atomicIntegerCounter.incrementByOne();
if ( currentNumber >= maxNumber ) {
break;
}
else {
numbers[currentNumber] = currentNumber;
}
}
countDownLatch.countDown();
}
}
Page Author: JD
Back: Atomic Classes | Concurrency