0

I've already read a great number of articles where is said that AtomicInteger class works faster than a synchronize construction. I did some tests on AtomicInteger and "synchronized" and in my tests, it occurs that synchronized is much faster than AtomicInteger. I would like to understand what is going wrong: my test class is incorrect or AtomicInteger works faster in other situations?

Here is my test class:

    public class Main {

    public static void main(String[] args) throws InterruptedException
    {
        // creating tester "synchronized" class
        TesterSynchronized testSyn = new TesterSynchronized();

        // Creating 3 threads
        Thread thread1 = new Thread(testSyn);
        Thread thread2 = new Thread(testSyn);
        Thread thread3 = new Thread(testSyn);

        // start time
        long beforeSyn = System.currentTimeMillis();

        // start
        thread1.start();
        thread2.start();
        thread3.start();

        thread1.join();
        thread2.join();
        thread3.join();

        long afterSyn = System.currentTimeMillis();
        long delta = afterSyn - beforeSyn;

        System.out.println("Test synchronized: " + delta + " ms");

        // _______________________________________________________

        // creating tester "atomicInteger" class
        TesterAtomicInteger testAtomic = new TesterAtomicInteger();

        thread1 = new Thread(testAtomic);
        thread2 = new Thread(testAtomic);
        thread3 = new Thread(testAtomic);

        // start time
        long beforeAtomic = System.currentTimeMillis();

        // start
        thread1.start();
        thread2.start();
        thread3.start();

        thread1.join();
        thread2.join();
        thread3.join();

        long afterAtomic = System.currentTimeMillis();
        long deltaAtomic = afterAtomic - beforeAtomic;

        System.out.println("Test atomic integer: " + deltaAtomic + " ms");
    }
}

// Synchronized tester
class TesterSynchronized implements Runnable {
    public int integerValue = 0;

    public synchronized void run() {
        for (int i = 0; i < 1_000_000; i++)
            integerValue++;
    }
}

// AtomicInteger class tester
class TesterAtomicInteger implements Runnable {
    AtomicInteger atomicInteger = new AtomicInteger(0);

    public void run() {
        for (int i = 0; i < 1_000_000; i++)
            atomicInteger.incrementAndGet();
    }
}

Test parameters: 3 threads and 1_000_000 increments; Result:

Test synchronized: 7 ms. Test atomic integer: 51 ms

I would be glad to understand why this is happening.

UPD The test will be correct if change the synchronized method to a synchronized block.

// Synchronized tester
class TesterSynchronized implements Runnable {
    public int integerValue = 0;

    public void run() {
        for (int i = 0; i < 1_000_000; i++) {
            synchronized (this) {
                integerValue++;
            }
        }
    }
}
gurmigou
  • 55
  • 6
  • In your synchronised tester you are taking one lock, and then doing your million increments. In your AtomicInteger example you are taking and releasing a lock for each increment. – tgdavies Dec 06 '20 at 21:35
  • Try to wrap the `synchronized` around `integerValue++` and `AtomicInteger` will be faster. You cannot compare the two methods. – Glains Dec 06 '20 at 21:37
  • Indeed, it is a problem! After changing to synchronized block the result of the test is: Test synchronized: 244 ms Test atomic integer: 95 ms. – gurmigou Dec 06 '20 at 21:59
  • Besides the mistake with `synchronized`, you should check out how to do [correct benchmarking in Java](https://stackoverflow.com/q/504103/12323248). In this test here it might be clear that `synchronized` is slower, but in some other tests you want to avoid noise that impacts your results. – akuzminykh Dec 07 '20 at 02:03
  • The original TesterSynchronized loop can be optimized by the compiler. There is no point in executing that loop; it can just immediately assign the 1_000_000 to integerValue instead of doing 1M increments. – pveentjer Dec 08 '20 at 16:24
  • I would also use JMH to reduce the chance of obvious benchmarking bugs. And keep in mind that a run of a few ms is very short; the JIT hardly had any time to warm up. Apart from that, the first 4 seconds biased locking (a lock optimization) is disabled which could color the numbers differently. – pveentjer Dec 08 '20 at 16:26

1 Answers1

2

The obvious difference in your code is that the AtomicIntger version allows interleaving of threads with ever access, whereas the synchronized version does the entire loop each thread in turn.

There may be other issues. For instance, the JVM may merge multiple invocations of a synchronized block. Depending on the platform, incrementAndGet may not be an atomic operation but implemented as a CAS-loop - if contention is high that could be a problem (I am not entirely sure on that).

Whichever way it is arranged, if you have multiple threads concurrently modifying the same memory location it will not be fast.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • "if contention is high that could be a problem (I am not entirely sure on that)", it's the opposite: CAS is especially great when contention is high, because it won't force threads to be blocked. – akuzminykh Dec 07 '20 at 01:45
  • @akuzminykh I think the issue is that with multiple concurrent threads can loop because one thread succeeded. Admittedly this is unlikely if the operation is a fast as just an increment, and it may well be implemented with locked load-modify-store. – Tom Hawtin - tackline Dec 07 '20 at 21:27