Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is AtomicInteger preferrable over synchronized?

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?

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()?

like image 651
ef2011 Avatar asked Jul 26 '12 13:07

ef2011


People also ask

Is AtomicInteger synchronized?

Yes, you are correct. AtomicInteger would not grant any benefit if all access to the object is synchronized (only one thread, at most, would be accessing its contents at any given moment).

Why is AtomicInteger class better than a synchronized counter class What is CAS?

AtomicInteger uses combination of volatile & CAS (compare and swap) to achieve thread-safety for Integer Counter. It is non-blocking in nature and thus highly usable in writing high throughput concurrent data structures that can be used under low to moderate thread contention.

What is AtomicInteger and when to use?

An AtomicInteger is used in applications such as atomically incremented counters, and cannot be used as a replacement for an Integer . However, this class does extend Number to allow uniform access by tools and utilities that deal with numerically-based classes.

What is difference between Integer and AtomicInteger?

Integers are object representations of literals and are therefore immutable, you can basically only read them. AtomicIntegers are containers for those values. You can read and set them. Same as asigning a value to variable.


2 Answers

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
like image 165
Peter Lawrey Avatar answered Sep 23 '22 15:09

Peter Lawrey


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)

like image 38
Andrey Borisov Avatar answered Sep 20 '22 15:09

Andrey Borisov