4

I testes how fast the Atomic Integer in multithread with comparing synchronized method, but I got the result that Atomic Integer was slower than synchronized method.

I read Java API Reference and I understood Atomic Integer is faster than synchronized in multithread.

So I want to know the reason why I got the poor result with Atomic Integer. My test code was wrong?

Here is my code

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicThreadTest {

    // Number of thread to run
    private static final int THREAD_NUM = 1000;
    // Repetition number to count up
    private static final int ROOP_NUM = 200000;

    // Base counter class
    private abstract class Counter implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < ROOP_NUM; i++) {
                increment();
            }
        }
        // Increment method
        public abstract void increment();
        // Get result of calculation
        public abstract int getResult();
        // Get name of the class
        @Override
        public String toString() {
            return getClass().getSimpleName();
        }
    }

    // Use no thread safe
    private static int count = 0;
    private class NoThreadSafeCounter extends Counter {
        @Override
        public void increment() {
            count++;
        }
        @Override
        public int getResult() {
            return count;
        }
    }

    // Use volatile
    private static volatile int volatileCount = 0;
    private class VolatileCounter extends Counter {
        @Override
        public void increment() {
            volatileCount++;
        }
        @Override
        public int getResult() {
            return volatileCount;
        }
    }

    // Use synchronized
    private static int synchronizedCount = 0;
    private class SynchronizedCounter extends Counter {
        @Override
        public synchronized void increment() {
            synchronizedCount++;
        }
        @Override
        public int getResult() {
            return synchronizedCount;
        }
    }

    // Use AtomicInteger
    private static AtomicInteger atomicCount = new AtomicInteger(0);
    private class AtomicCounter extends Counter {
        @Override
        public void increment() {
            atomicCount.incrementAndGet();
        }
        @Override
        public int getResult() {
            return atomicCount.get();
        }
    }

    public static void main(String[] args) {
        AtomicThreadTest testClass = new AtomicThreadTest();
        Counter[] testCounter = {
                testClass.new NoThreadSafeCounter(),
                testClass.new VolatileCounter(),
                testClass.new SynchronizedCounter(),
                testClass.new AtomicCounter(),
        };

        for (Counter c : testCounter) {
            System.out.println("-------------------------");
            System.out.printf("Test for class : %s\n", c.toString());
            try {
                test(c);
            } catch (InterruptedException e) {
                System.out.println("Test halted");
            } finally {
                System.out.println("");
                System.gc();
            }
        }
        System.out.println("-------------------------");
    }

    public static void test(final Counter counter) throws InterruptedException {
        System.out.printf("Start with threads : %d, roop : %d\n", THREAD_NUM, ROOP_NUM);
        final long startTime = System.currentTimeMillis();

        // Create THREAD_NUM threads and run them
        Thread[] threads = new Thread[THREAD_NUM];

        for (int i = 0; i < THREAD_NUM; i++) {
            threads[i] = new Thread(counter);
            threads[i].start();
        }

        // Wait for all threads other than this end
        while (Thread.activeCount() > 1) {
            Thread.sleep(10);
        }

        final long endTime = System.currentTimeMillis();
        System.out.printf("Result %d, Expected %d\n", counter.getResult(), THREAD_NUM*ROOP_NUM);
        System.out.printf("Time to calc : %d\n", endTime-startTime);
    }
}

And I got the result below.

-------------------------
Test for class : NoThreadSafeCounter
Start with threads : 1000, roop : 200000
Result 198785583, Expected 200000000
Time to calc : 127

-------------------------
Test for class : VolatileCounter
Start with threads : 1000, roop : 200000
Result 19162116, Expected 200000000
Time to calc : 4458

-------------------------
Test for class : SynchronizedCounter
Start with threads : 1000, roop : 200000
Result 200000000, Expected 200000000
Time to calc : 8426

-------------------------
Test for class : AtomicCounter
Start with threads : 1000, roop : 200000
Result 200000000, Expected 200000000
Time to calc : 15190
user2738844
  • 169
  • 3
  • 10
  • 3
    You should not call `System.gc()` as you don't know when it will get executed. You are not using a lot of memory, it is unlikely that you ever need to GC during the execution – Dici Sep 06 '15 at 10:26
  • 2
    BTW your synchronization (the `while` loop at the end) is bad and consumes a lot of CPU. You should use something like a semaphore instead – Dici Sep 06 '15 at 10:28
  • @Dici Thank you for the reply. For system.gc(), I read my code again and understood that each Counter object is very simple and does consume very few memories. So, I need not to call `System.gc()`. Is it right? – user2738844 Sep 06 '15 at 11:28
  • 2
    I have completely different results than you with a JMH benchmark. See it in [Github Gist](https://gist.github.com/anonymous/ba713d09ae387979d5d2). I have the expected result that AtomicInteger is faster than synchronized. – Tunaki Sep 06 '15 at 11:30
  • And for the while loop you mentioned at second comment, Do you mean that `while (Thread.activeCount() > 1) { Thread.sleep(10); }` in the `test` method consumes a lot of CPU? If so, would you mind teaching me the reason why that part consumes a lot of CPU? – user2738844 Sep 06 '15 at 11:32
  • 2
    @user2738844 because you are pinging your CPU every 10 ms to check if the computation is over whereas you could just wait for the completion of all threads with a semaphore without pinging the CPU – Dici Sep 06 '15 at 11:37
  • @Tunaki Thank you for the reply. I'm sorry I have not installed open jdk :( So I will install it and test your code ASAP! – user2738844 Sep 06 '15 at 11:38
  • @Dici I think you are right. But I can not understand why semaphore consumes little CPU while waiting because of my lack of knowledge about multi threading X( Anyway, thank you for your kind reply! – user2738844 Sep 06 '15 at 11:53
  • 3
    In the case of a semaphore, your main thread is going to be blocked until the completion of all tasks. While it is blocked, it will not consume any CPU time whereas your loop is *actively waiting*, this means that the main thread actively wakes up, sleep, wakes up, sleep etc at a fast frequency. – Dici Sep 06 '15 at 11:59
  • @Dici I know understand why it is better to use semaphore than the loop I used. Thank you very much. – user2738844 Sep 06 '15 at 13:58

1 Answers1

3

Despite of the code issues of your testcase, let's talk about the multithreading problem itself.

If you set THREAD_NUM to a much lower number such as 8 or 4, you will find that your AtomicCounter is a bit faster that SynchronizedCounter. Running with 1000 threads will cost two much CPU on AtomicInteger's CAS (compare-and-swap), which cause it runs slower than synchronized code block.

To prove this, you can implement a LongadderCounter:

private class LongadderCounter extends Counter {

    @Override
    public void increment() {
        longadder.increment();
    }

    @Override
    public int getResult() {
        return longadder.intValue();
    }
}

You'll find that LongadderCounter much faster. LongAdder to AtomicInteger is ConcurrentHashMap to Collections.synchronizedMap(new HashMap<>()), it divides the counter into multiple parts and do CAS on each part to mitigate race conditions.

Lii
  • 11,553
  • 8
  • 64
  • 88
Weibo Li
  • 3,565
  • 3
  • 24
  • 36
  • I was surprised at the speed of LongAdder! It took 1/15 of the time with AtomicInteger. And thank you for courteous explanation! – user2738844 Sep 06 '15 at 13:53