When is AtomicInteger preferrable over synchronized?
Solution 1
Since AtomicInteger can be at at least an order of magnitude slower than an int protected by synchronized, why would I ever want to use AtomicInteger?
AtomicInteger is much faster.
static final Object LOCK1 = new Object();
static final Object LOCK2 = new Object();
static int i1 = 0;
static int i2 = 0;
static final AtomicInteger ai1 = new AtomicInteger();
static final AtomicInteger ai2 = new AtomicInteger();
public static void main(String... args) throws IOException {
for(int i=0;i<5;i++) {
testSyncInt();
testAtomicInt();
}
}
private static void testSyncInt() {
long start = System.nanoTime();
int runs = 10000000;
for(int i=0;i< runs;i+=2) {
synchronized (LOCK1) {
i1++;
}
synchronized (LOCK2) {
i2++;
}
}
long time = System.nanoTime() - start;
System.out.printf("sync + incr: Each increment took an average of %.1f ns%n", (double) time/runs);
}
private static void testAtomicInt() {
long start = System.nanoTime();
int runs = 10000000;
for(int i=0;i< runs;i+=2) {
ai1.incrementAndGet();
ai2.incrementAndGet();
}
long time = System.nanoTime() - start;
System.out.printf("incrementAndGet: Each increment took an average of %.1f ns%n", (double) time/runs);
}
prints
sync + incr: Each increment took an average of 32.4 ns
incrementAndGet: Each increment took an average of 20.6 ns
sync + incr: Each increment took an average of 31.4 ns
incrementAndGet: Each increment took an average of 12.9 ns
sync + incr: Each increment took an average of 29.6 ns
incrementAndGet: Each increment took an average of 12.9 ns
sync + incr: Each increment took an average of 35.1 ns
incrementAndGet: Each increment took an average of 16.6 ns
sync + incr: Each increment took an average of 29.9 ns
incrementAndGet: Each increment took an average of 13.0 ns
Adding some contention as @assylias suggests. It shows that when you are only really using one thread the CPU can optimise the access.
static final Object LOCK1 = new Object();
static final Object LOCK2 = new Object();
static int i1 = 0;
static int i2 = 0;
static final AtomicInteger ai1 = new AtomicInteger();
static final AtomicInteger ai2 = new AtomicInteger();
public static void main(String... args) throws ExecutionException, InterruptedException {
for(int i=0;i<5;i++) {
testSyncInt();
testAtomicInt();
}
}
private static void testSyncInt() throws ExecutionException, InterruptedException {
long start = System.nanoTime();
final int runs = 1000000;
ExecutorService es = Executors.newFixedThreadPool(2);
List<Future<Void>> futures = new ArrayList<>();
for(int t=0;t<8;t++) {
futures.add(es.submit(new Callable<Void>() {
public Void call() throws Exception {
for (int i = 0; i < runs; i += 2) {
synchronized (LOCK1) {
i1++;
}
synchronized (LOCK2) {
i2++;
}
}
return null;
}
}));
}
for (Future<Void> future : futures) {
future.get();
}
es.shutdown();
long time = System.nanoTime() - start;
System.out.printf("sync + incr: Each increment took an average of %.1f ns%n", (double) time/runs/2);
}
private static void testAtomicInt() throws ExecutionException, InterruptedException {
long start = System.nanoTime();
final int runs = 1000000;
ExecutorService es = Executors.newFixedThreadPool(2);
List<Future<Void>> futures = new ArrayList<>();
for(int t=0;t<8;t++) {
futures.add(es.submit(new Callable<Void>() {
public Void call() throws Exception {
for (int i = 0; i < runs; i += 2) {
ai1.incrementAndGet();
ai2.incrementAndGet();
}
return null;
}
}));
}
for (Future<Void> future : futures) {
future.get();
}
es.shutdown();
long time = System.nanoTime() - start;
System.out.printf("incrementAndGet: Each increment took an average of %.1f ns%n", (double) time/runs/2);
}
prints
sync + incr: Each increment took an average of 478.6 ns
incrementAndGet: Each increment took an average of 191.5 ns
sync + incr: Each increment took an average of 437.5 ns
incrementAndGet: Each increment took an average of 169.8 ns
sync + incr: Each increment took an average of 408.1 ns
incrementAndGet: Each increment took an average of 180.8 ns
sync + incr: Each increment took an average of 511.5 ns
incrementAndGet: Each increment took an average of 313.4 ns
sync + incr: Each increment took an average of 441.6 ns
incrementAndGet: Each increment took an average of 219.7 ns
Solution 2
if you really want to get more details on why java.util.concurrent
stuff is better and what the difference is compared to the classical synchronized approach, read this link (and the whole blog generally)
ef2011
Updated on June 13, 2022Comments
-
ef2011 almost 2 years
Since
AtomicInteger
can be at at least an order of magnitude slower than anint
protected bysynchronized
, why would I ever want to use AtomicInteger?For example, if all I want is to increment an
int
value in a thread-safe manner, why not always use:synchronized(threadsafeint) { threadsafeint++; }
instead of using the much slower AtomicInteger.incrementAndGet()?
-
ef2011 almost 12 yearsI'm now really confused. That's not what I understood from @Gray's answer here: stackoverflow.com/a/11125474/722603 What am I missing?
-
Ernest Friedman-Hill almost 12 yearsI think he means that
AtomicInteger
is slower than an unsynchronizedint
; he's talking about why you shouldn't replace all yourint
members withAtomicInteger
without justification. But it's not an order of magnitude slower, any more than it's an order of magnitude faster. For the most part these are all small differences we're talking about. -
kosa almost 12 yearsI remember reading somewhere that locking mechanism also different for Atomic stuff, but couldn't get that reference right now. I may be wrong too.
-
Stephen C almost 12 years@thinksteep - Atomic types typically don't use locking. They use atomic "compare and swap" instructions, and the like. Reference: ibm.com/developerworks/java/library/j-jtp11234
-
kosa almost 12 years@StephenC: Thanks for the link it will refresh my understanding.
-
assylias almost 12 years@PeterLawrey You have been gentle - you could have added a little bit of contention to make the case of the CAS vs. monitor ;-)
-
ef2011 almost 12 yearsWow. If so, the answer is simple: When you need that extra performance and
AtomicInteger
can still satisfy thread safety requirements (although this could be tricky sometimes in regard to race conditions). Did I get this right this time? -
Vishy almost 12 years@assylias It slowed down the updates significantly, but the relative result was the same.
-
Vishy almost 12 years@ef2011 I would agree that AtomicInteger is the best choice if it does exactly what you need. If you have other operations you need, it might not be.
-
assylias almost 12 years@PeterLawrey Interesting - I would have thought differently. Thanks for the update.
-
assylias almost 12 yearsChecking JCiP 15.3.2 - the effect seems more noticeable with moderate contention but it can reverse at high contention levels.
-
Vishy almost 12 yearsAt 8 threads on 8 cores, the performance is the same. If this is all your program does, you need a redesign IMHO. ;)