Performance Difference of AtomicInteger vs Integer
Came across this posting today but wanted to share my results (Please no comments on code as I had to hand type the following classes as the system I ran this one on was not connected to the internet :)
Bottom line the output from the code below was as follows:
ATOMIC Results: Elapsed = 25257 ms, ExpectedValue = 50000, FinalValue = 50000, true PrImItIvE Results: Elapsed = 25257 ms, ExpectedValue = 50000, FinalValue = 48991, false
For my usage in my particular app I have chosen to use Atomic values for status numbers in a monitoring class. In case someone else wanted to view some hard results I opted to post this information.
Have a great day!
Classes:
I created a main class with a primitive long and an atomic long and accessor increment methods, an IncrementAtomicRunnable and an IncrementPrimitiveRunnable.
LongOverhead:
public class LongOverhead{
AtomicLong atomicLong;
long primitiveLong;
public LongOverhead(){
atomicLong = new AtomicLong(0l);
primitiveLong = 0l;
}
public void incrAtomicLong(){
atomicLong.getAndAdd(1l);
}
public long getAtomicLong(){
return atomicLong.get();
}
public void incrPrimitiveLong(){
primitiveLong++;
}
public long getPrimitiveLong(){
return primitiveLong;
}
public static void main(String [] args){
String template = "%s Results: Elapsed = %d ms, ExpectedValue = %d, FinalValue = %d, %b";
int loopTotal = 1000;
int waitMilliseconds = 25;
int totalThreads = 50;
int expectedValue = loopTotal * totalThreads;
int whileSleep = 250;
LongOverhead atomic = new LongOverhead();
LongOverhead primitive = new LongOverhead();
List<Thread> atomicThreads = new ArrayList<>();
List<Thread> primitiveThreads = new ArrayList<>();
for(int x=0;x<totalThreads;x++){
Thread a = new Thread(new IncrementalAtomicRunnable(atomic, loopTotal, waitMilliseconds), "AtomicIncr" + x);
atomicThreads.add(a);
Thread p = new Thread(new IncrementalPrimitiveRunnable(primitive, loopTotal, waitMilliseconds), "PrimitiveIncr" + x);
primitiveThreads.add(p);
}
boolean cont = true;
long atomicStart = System.currentTimeMillis();
for(Thread t: atomicThreads){
t.start();
}
while(cont){
try{
Thread.sleep(whileSleep);
}catch(InterruptedException e){
e.printStackTrace();
}
boolean foundAlive = false;
for(Thread t: atomicThreads){
foundAlive = (State.TERMINATED != t.getState());
if(foundAlive){
break;
}
}
cont = foundAlive;
}
long atomicFinish = System.currentTimeMillis();
long atomicElapsed = atomicFinish - atomicStart;
long atomicFinal = atomic.getAtomicLong();
cont = true;
long primitiveStart = System.currentTimeMillis();
for(Thread t: primitiveThreads){
t.start();
}
while(cont){
try{
Thread.sleep(whileSleep);
}catch(InterruptedException e){
e.printStackTrace();
}
boolean foundAlive = false;
for(Thread t: primitiveThreads){
foundAlive = (State.TERMINATED != t.getState());
if(foundAlive){
break;
}
}
cont = foundAlive;
long primitiveFinish = System.currentTimeMillis();
long primitiveElapsed = primitiveFinish - primitiveStart;
long primitiveFinal = primitive.getPrimitiveLong();
System.out.println(String.format(template, "ATOMIC", atomicElapsed, expectedValue, atomicFinal, (expectedValue==atomicFinal)));
System.out.println(String.format(template, "PrImItIvE", primitiveElapsed, expectedValue, primitiveFinal, (expectedValue==primitiveFinal)));
}
IncrementAtomicRunnable:
public class IncrementAtomicRunnable implements Runnable{
protected LongOverhead oh;
protected int loopTotal;
protected int waitMilliseconds;
protected String currentThreadName;
public IncrementAtomicRunnable(LongOverhead oh, int loopTotal, int waitMilliseconds){
this.oh = oh;
this.loopTotal = loopTotal;
this.waitMilliseconds = waitMilliseconds;
}
@Override
public void run(){
currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " for ATOMIC is starting.....");
for(int x=0;x<loopTotal;x++){
oh.incrAtomicLong();
try{
Thread.sleep(waitMilliseconds);
}catch(InterruptedException e){
System.out.println("InterruptedThread[" + currentThreadName + "], eating exception @@@@@");
}
}
System.out.println("....." + currentThreadName + " for ATOMIC is finished.");
}
}
and finally IncrementPrimitiveRunnable:
public class IncrementPrimitiveRunnable extends IncrementAtomicRunnable{
public IncrmentPrimitiveRunnable(LongOverhead oh, int loopTotal, int waitMilliseconds){
super(oh, loopTotal, waitMilliseconds);
}
@Override
public void run(){
super.currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " for PRIMITIVE is starting.....");
for(int x=0;x<loopTotal;x++){
oh.incrPrimitiveLong();
try{
Thread.sleep(waitMilliseconds);
}catch(InterruptedException e){
System.out.println("InterruptedThread[" + currentThreadName + "], eating exception @@@@@");
}
}
System.out.println("....." + currentThreadName + " for PRIMITIVE is finished.");
}
}
The choice of these two types should not depend on the performance. The main choice for AtomicInteger
is if you want to achieve thread safety with the operations on the integer.
However the performace difference might strongly depend on the choosen operating system, as the detailed implementation of atomic operations depend on the operating system.
Well, if you use it in multithreaded environment, as a, e.g. counter, then you have to synchronize
access to the Integer
public final class Counter {
private long value = 0;
public synchronized long getValue() {
return value;
}
public synchronized long increment() {
return ++value;
}
}
While you can have much better performance with AtomicInteger without synchronization
public class NonblockingCounter {
private AtomicInteger value;
public int getValue() {
return value.get();
}
public int increment() {
return value.incrementAndGet();
}
}
Recommended reading http://cephas.net/blog/2006/09/06/atomicinteger/
EDIT use incrementAndGet
AtomicInteger
allows some (not all!) operations that would otherwise require synchronization to be performed in a lock-free manner using special hardware instructions. How this affects performance is somewhat complex:
- First, it's a micro-optimization that will only matter if this particular operation is on your application's critical path.
- The special hardware instructions may not be available on non-mainstream platforms, in which case
AtomicInteger
will probably be implemented using synchronization. - The JVM can often optimize away locking overhead when there is no contention (e.g., a single threaded application). In that case, there's probably no difference.
- If there is low to moderate lock contention (i.e. multiple threads, but they mostly do other things than just accessing that integer), the lock-free algorithm performs better than synchronization.
- If there is very heavy lock contention (i.e. lots of threads that spend a lot of time trying to access that integer), synchronization may perform better because the lock-free algorithm is based on constantly retrying the operation when it fails due to a collision.