Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Atomic variables vs synchronized methods

I have a counter class which has increment and decrement method, both the methods are synchronized.

public class Counter {
   int count = 0;
   public synchronized void increment(){
       count++;
   }

   public synchronized void decrement(){
       count--;
   }
}

Its very clear from this example that race condition won't happen, only one thread can access increment or decrement method.

Now instead of integer primitives, if I modified the counter class with Atomic Integer and removed the synchronized keyword, can we achieve the same thing ?

public class Counter {
    AtomicInteger count = new AtomicInteger();

    public void increment(){
       count.incrementAndGet();
    }

    public void decrement(){
       count.decrementAndGet();
    }
}
like image 756
Kuldeep Avatar asked May 21 '18 07:05

Kuldeep


2 Answers

Starting with java-8 there is a better (read faster in really contended environments) option instead of AtomicInteger, it's called LongAdder and it guarantees atomic updates in the same manner:

Under low update contention, the two classes have similar characteristics (AtomicLong). But under high contention, expected throughput of this class is significantly higher, at the expense of higher space consumption.

Its implementation is superb. In plain english (simplified): if there is no contention between threads, don’t be a smart ass and simply work as with AtomicLong; if there is, try to create a separate working space for each thread, so that contention is minimized.

It has the same methods you care about: add and decrement.

like image 165
Eugene Avatar answered Oct 11 '22 14:10

Eugene


Well, this is the kind of question where you could write a quick 'n' dirty program to evaluate:

public class ThreadExperiment {

    public static class CounterSync {
        int count = 0;
        public synchronized void increment(){
            count++;
        }

        public synchronized void decrement(){
            count--;
        }
    }

    public static class CounterAtomic {
        AtomicInteger count = new AtomicInteger();

        public  void increment(){
            count.incrementAndGet();
        }

        public void decrement(){
            count.decrementAndGet();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final CounterSync counterSync = new CounterSync();
        final CounterAtomic counterAtomic = new CounterAtomic();

        Runnable runnable = () -> {
            for (int i = 0; i < 1_000_000; i++) {
                int j = i & 1;
                if (j == 0) {
                    counterSync.increment();
                    counterAtomic.increment();
                } else {
                    counterSync.decrement();
                    counterAtomic.decrement();
                }
            }
        };

        Thread[] threads = new Thread[10];

        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(runnable);
        }

        for (Thread t : threads)
            t.start();

        for (Thread t : threads)
            t.join();

        System.out.println("Sync count = " + counterSync.count);
        System.out.println("Atomic count = " + counterAtomic.count.intValue());
    }
}

In the end, both counters should and will be 0. By removing the "synchronized" keywords, you will see that things go completely wrong.

So yes, both achieve the same thing: thread safeness.

Oracle documents the example here: https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html

For this simple class, synchronization is an acceptable solution. But for a more complicated class, we might want to avoid the liveness impact of unnecessary synchronization. Replacing the int field with an AtomicInteger allows us to prevent thread interference without resorting to synchronization

In the end, the behaviour of your methods will make the difference if you need explicit synchronization or if atomic fields will be enough.

like image 32
Marcus K. Avatar answered Oct 11 '22 13:10

Marcus K.