Tuesday, November 10, 2020

Difference between atomic, volatile and synchronized in Java?

Hello guys, a lot of people are asking me about the volatile, synchronized, and volatile variables in Java concurrency. After answering them individually on Facebook and LinkedIn, I thought to write this article. In this Java multi-threading tutorial, we will learn about the difference between atomic, volatile, and synchronized variables in Java. Though there are a lot of articles, posts, books, courses, and tutorials already exist on Java concurrency and synchronization, where different people have tried to explain concurrency concepts, but unfortunately, multi-threading and concurrency concepts are still hard to grasp, especially the volatile variables.

Even though all three, atomic, volatile, and synchronized helps to avoid multi-threading issues, they are entirely different from each other. Before seeing the difference between them, let's understand what does synchronized, volatile, and atomic variables in Java provides.

1. Synchronized keyword in Java

1) Provides mutual exclusion - two threads can not run a synchronized method or block at the same time. Though beware of using static and non-static synchronized methods together.

2) Provides visibility guaranteed - updated values of variables modified inside synchronized context will be visible to all thread

3) A synchronized keyword also provides blocking. A thread will block until the lock is available, before entering to code protected by a synchronized keyword. See how synchronization works in Java to know all about synchronized keyword in Java.

4) As per the happens-before rule, an unlock on a monitor happens-before every subsequent lock on the same monitor.





2. Volatile keyword in Java

1) It provides a visibility guarantee. As per the happens-before rule, write to volatile variable happens before every subsequent read of the same variable.

2) It also prevents Compiler from doing smart things, which can create problems in a multi-threading environment, like caching variables, re-ordering of code, etc. You can also check how volatile variable works in Java to know more about volatile variables.



3. Atomic classes like AtomicInteger, AtomicLong, and AtomicReference

1) Atomic variables in Java also provides the same memory semantics as a volatile variable, but with an added feature of making compound action atomic.

2) It provides a convenient method to perform atomic increment, decrement, CAS operations. See Javadoc for java.util.concurrent.atomic package and individual classes.

3) Useful methods are addAndGet(int delta), compareAndSet(int expect, int update), incrementAndGet() and decrementAndGet()


These were some of the main points about synchronized, volatile, and Atomic variables in Java which every Java developer should know and remember. If you are new to Java and want to learn these important concepts in more detail, I suggest you join a comprehensive Java course.

If you need a reference, I recommend The Complete Java Masterclass course by Tim Buchalaka and his team on Udemy. It's also one of the most up-to-date courses and covers new features from recent Java releases.

atomic vs  volatile vs synchronized in Java


4. Difference between volatile, atomic, and synchronized in Java

Most of the difference between these three synchronization construct comes from the fact mentioned above. Though all three offer the same memory semantics guaranteed by the happens-before rule and visibility guarantee, there is a significant difference between synchronized vs. atomic and volatile variables.

1) The first and significant difference between synchronized keyword and volatile and atomic variables comes in terms of locking. The synchronized keyword is used to implement a lock-based concurrent algorithm, and that's why to suffer from the limitation of locking. A volatile and atomic variable gives you the power to implement non-blocking algorithms, which is more scalable.

2) On the difference between atomic and volatile variables, they provide the same visibility guarantee, but the atomic variable also provides the ability to make compound action, like the read-modify-write atomic. The volatile variable can not be used when one variable's value depends upon others or its own increment value.

3) Atomic variables perform better because they use concurrency support provided by hardware for various atomic operations, like compare-and-swap or read-modify-write.

4) Because of non-blocking nature, atomic variables are immune to concurrency hazards such as deadlock and livelock.

5) Another significant difference between synchronized, volatile, and atomic variables comes in terms of performance. Atomic variables perform better than synchronized keyword because of no overhead of acquisition and release of the lock.

This difference may not be visible in an uncontended environment, but when multiple threads try for the same lock and the same time, you can see performance degradation due to context switching, putting the thread on sleep, and then resuming, etc.

Apart from the differences mentioned above, there is a couple of syntactic difference between synchronized, volatile and atomic variables, like volatile keyword is only applicable to variables, and synchronized keyword can only be used with methods and blocks.

The key thing to learn is when to use volatile, synchronized, and atomic variables in Java, as incorrect understanding can cause serious bugs and performance impact. If you want to learn more or deep dive into Concurrency and Performance topic, I also recommend you check out Java Multithreading, Concurrency, and Performance Optimization course on Udemy. It's one of the best course on the topic and highly recommend this course to every experienced Java programmers.

Difference between atomic, volatile and synchronized in Java?




5. Java multi-threading Example - Counter Class

As I told you, the key thing to learn is when to use volatile, synchronized, and atomic variables in Java. Let's see an example of a multi-threaded integer counter, whose invariant is to return a count and increment it by one. Ideally, every call to the getCount() method must return a different value, and no update should be missed.

Let's see what happens in the following example

If you look at the code below, you will find that, It's a non synchronized counter. Since access to the count variable is not synchronized, many updates to count will be missed. If you run below the program multiple times, you will get a different count.

To provided maximum interleaving, we have used CyclicBarrier; there are two of them, one is for starting the test, and the other is for printing results. StartingBarrier will ensure that no thread will call getCount() method until all threads are started by waiting for them at the barrier. EndingBarrier will allow the main thread to call the getCount() method after all threads have finished updating the counter.

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class AtomicVolatileSynchronizedJava {
 
    public static void main(String args[]){
        final IntegerCounter counter = new IntegerCounter();
        final CyclicBarrier startingBarrier = new CyclicBarrier(5);
        final CyclicBarrier endingBarrier = new CyclicBarrier(6);
     
        Runnable task = new Runnable(){
            @Override
            public void run(){
                try {
                    startingBarrier.await();                
                    for (int i = 0; i < 1000; i++) {
                        int count = counter.getCount();
                    }                
                    endingBarrier.await();
                 
                } catch (InterruptedException ex) { ex.printStackTrace();
                } catch (BrokenBarrierException ex) {ex.printStackTrace();
                }  
            }
        };
     
        Thread T1 = new Thread(task); T1.start();
        Thread T2 = new Thread(task); T2.start();
        Thread T3 = new Thread(task); T3.start();
        Thread T4 = new Thread(task); T4.start();
        Thread T5 = new Thread(task); T5.start();
     
        try {
            endingBarrier.await();
            System.out.println("Expected count : 5001" + " Actual count : "
                                + counter.getCount());
        } catch (InterruptedException ex) { ex.printStackTrace();
        } catch (BrokenBarrierException ex) {ex.printStackTrace();
        }
    }

}
/**
  * A counter, which should return different count each time
  */
class IntegerCounter{
    private int count = 1;
 
    public int getCount(){
        return count++;
    }
}

Output:
Execution 1 : Expected count : 5001 Actual count : 4553
Execution 2 : Expected count : 5001 Actual count : 4966
Execution 3 : Expected count : 5001 Actual count : 4879

Now, how do you fix this counter? Should we make the count variable volatile? or make the getCount() method synchronized, or use AtomicInteger instead of int as a count variable.

Let's try with a volatile first. As per the theory, volatile provides a visibility guarantee, so the change made by one Thread should be visible to others (see Java Concurrency in Practice - The Book)

Now we have private volatile int count = 1; on IntegerCounter class, and, here is the result of our three more iterations.

Expected count : 5001 Actual count : 4174
Expected count : 5001 Actual count : 4205
Expected count : 5001 Actual count : 4293

What do you think? Why does making volatile doesn't the IntegerCounter here? 

Because the volatile variable doesn't provide an atomicity guarantee. count++  is a three-step operation.

1) Read the value of the count variable
2) Add 1 to count variable (modify)
3) Write modified value into count

Even with the volatile variables, It may be possible that two threads may see the same value of count and goes on to update that. To solve this issue, we can make the getCount() method synchronized.

By making synchronized, we got locking and visibility. When the thread leaves the synchronized method, All threads will see the updated value of the count variable. Let's re-run the code after making getCount() synchronized.

Expected count : 5001 Actual count : 5001
Expected count : 5001 Actual count : 5001
Expected count : 5001 Actual count : 5001

You can now see the consistent and expected value of the counter.

But, synchronized is expensive and affects performance considerably.

Java 5 provides another alternative for more frequent concurrent operations, like the CAS(Compare And Swap) and counter increment. Atomic classes from java.util.concurrent package, like AtomicInteger, AtomicLong, and AtomicReference, provides a convenient method to perform certain operations atomically.

These methods are faster than synchronized methods because they tend to use the fastest concurrency construct provided by hardware, and many modern CPU has primitive for atomic read-modify-write operations. (see Java Multithreading, Concurrency & Performance Optimization)

Though atomic variables are not a replacement for synchronized or volatile keyword, it suits perfectly in some cases, where a critical section is limited to updating a single variable, like in this shared counter-example, using an atomic variable allows you to write lock-free and more scalable code in Java.

Here is our IntegerCounter class, rewritten using AtomicInteger class :

class IntegerCounter{
    private AtomicInteger count = new AtomicInteger(1);
 
    public int getCount(){
        return count.getAndIncrement();
    }
}

and here is the output of three execution :

Expected count : 5001 Actual count : 5001
Expected count : 5001 Actual count : 5001
Expected count : 5001 Actual count : 5001


You can see that the count now matching and there is no discrepancy and unpredictable behavior.

That's all on the difference between the atomic, volatile, and synchronized variables in Java. Though they all provide some sort of synchronization and concurrency support, they are totally different from each other.

Be careful with volatile variables; in many cases, you would be tempted to use volatile variables instead of synchronized, but you might end of with bugs or the amount of testing required negates gain in performance.

Use the atomic variable whenever a critical section includes updating a single variable, it provides better performance than the synchronizing method. Last but not least, whenever in doubt, use a synchronized keyword, because it's more important to write correct code than faster code, and if you want to learn the following resources are best to understand these complex concurrency concepts.

Further Learning
Multithreading and Parallel Computing in Java
Java Concurrency in Practice - The Book
Applying Concurrency and Multi-threading to Common Java Patterns
Java Multithreading, Concurrency & Performance Optimization


Other Java Concurrency Articles you may like
  • The 2020 Java Developer RoadMap (roadmap)
  • 5 Courses to Learn Java Multithreading in-depth (courses)
  • 10 Java Multithreading and Concurrency Best Practices (article)
  • Top 50 Multithreading and Concurrency Questions in Java (questions)
  • 10 Courses to learn Java in 2020 (courses)
  • Difference between CyclicBarrier and CountDownLatch in Java? (answer)
  • How to avoid deadlock in Java? (answer)
  • Understanding the flow of data and code in Java program (answer)
  • Is Java Concurrency in Practice still valid in 2020 (answer)
  • How to do inter-thread communication in Java using wait-notify? (answer)
  • 10 Tips to become a better Java Developer in 2020 (tips)
  • 5 Essential Skills to Crack Java Interviews (skills)
  • How does Exchanger works in Java Multithreading (tutorial)
  • Top 5 Books to Master Concurrency in Java (books)

Thanks for reading this article so far. If you like this article then please share it with your friends and colleagues. If you have any questions or feedback then please drop a note.

P. S. - If you are new to the Java world and looking for some free courses to kick-start your Java programmer journey then you can also check out this list of top 10 free Java courses for beginners to start with.

No comments :

Post a Comment