Table of Contents
Overview and Key Facts
- You may take into account the following JAVA features
- Using a static volatile int like : volatile int num = 0; –> NOT WORKING
- Use a synchronized method
- Using AtomicInteger like : final AtomicInteger num = new AtomicInteger();
- The Java volatile keyword guarantees visibility of changes to variables across threads
- Using volatile is not enough to create thread safe code
Lets assume we have 2 threads incrementing a global volatile int as our Thread Counter Thread 1 could read a shared counter variable with the value 20 into its CPU cache, increment it to 21 Thread 2 could read a shared counter variable with the value 20 into its CPU cache, increment it to 21 Thread 1 writes Thread Counter value [ which is 21 ] to main memory Thread 2 writes Thread Counter value [ which is 21 ] to main memory Despite we have incremented our Counter 2x the counter value increases only by 1 ! This problem can occur as the ++ operator is not thread safe !
- AtomicInteger uses combination of volatile & CAS for thread-safe implementation of Integer Counter.
- AtomicInteger class stores its value field in a volatile variable, thus it is a decorator over the traditional volatile variable
- AtomicInteger provides unique non-blocking mechanism for updating the value after requiring the hardware level support for CAS (compare and set).
- Syncronized access is using locks and therefore slower than CAS method used by AtomicInteger
JAVA test case : IncrementNotThreadSafeMain.java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class IncrementNotThreadSafeMain {
public static void main(String... args) throws InterruptedException {
for (int nThreads = 1; nThreads <= 64; nThreads *= 2)
doThreadSafeTest(nThreads);
}
static class VolatileInt {
volatile int num = 0;
}
private static void doThreadSafeTest(final int nThreads) throws InterruptedException {
final int count = 100 * 1000 * 1000;
ExecutorService es = Executors.newFixedThreadPool(nThreads);
final VolatileInt vi = new VolatileInt();
System.out.printf("--- Testing with Volatile --- ");
for (int i = 0; i < nThreads; i++)
es.submit(new Runnable() {
public void run() {
for (int j = 0; j < count; j += nThreads)
vi.num++;
}
});
es.shutdown();
es.awaitTermination(1, TimeUnit.MINUTES);
assert es.isTerminated();
System.out.printf("With %,d threads should total %,d but was %,d%n", nThreads, count, vi.num /*num.longValue()*/);
System.out.printf("--- Testing AtomicInteger --- ");
es = Executors.newFixedThreadPool(nThreads);
final AtomicInteger num = new AtomicInteger();
for (int i = 0; i < nThreads; i++)
es.submit(new Runnable() {
public void run() {
for (int j = 0; j < count; j += nThreads)
num.incrementAndGet();
}
});
es.shutdown();
es.awaitTermination(1, TimeUnit.MINUTES);
assert es.isTerminated();
System.out.printf("With %,d threads should total %,d but was %,d%n", nThreads, count, num.longValue() );
}
}
Running above Code
$ java IncrementNotThreadSafeMain
--- Testing with Volatile --- With 1 threads should total 100,000,000 but was 100,000,000
--- Testing AtomicInteger --- With 1 threads should total 100,000,000 but was 100,000,000
--- Testing with Volatile --- With 2 threads should total 100,000,000 but was 54,089,900
--- Testing AtomicInteger --- With 2 threads should total 100,000,000 but was 100,000,000
--- Testing with Volatile --- With 4 threads should total 100,000,000 but was 44,019,787
--- Testing AtomicInteger --- With 4 threads should total 100,000,000 but was 100,000,000
--- Testing with Volatile --- With 8 threads should total 100,000,000 but was 24,731,449
--- Testing AtomicInteger --- With 8 threads should total 100,000,000 but was 100,000,000
--- Testing with Volatile --- With 16 threads should total 100,000,000 but was 28,908,662
--- Testing AtomicInteger --- With 16 threads should total 100,000,000 but was 100,000,000
--- Testing with Volatile --- With 32 threads should total 100,000,000 but was 37,063,611
--- Testing AtomicInteger --- With 32 threads should total 100,000,000 but was 100,000,000
--- Testing with Volatile --- With 64 threads should total 100,000,000 but was 79,022,174
--- Testing AtomicInteger --- With 64 threads should total 100,000,000 but was 100,000,000
--> All Volatile tests with more then 2 Threads are failing !
there’s also LongAdder and LongAccumulator, which may perform much better in extreme circumstances