Thursday, February 20, 2014

Atomic operations in Java

Before jumping to the topic;  let's first understand the keyword, Atom. Wikipedia defines Atom as something that can not be divided further (i.e. uncuttable or indivisible).

Java and other modern programming languages use the term atomicity for operations/methods, which gets executed as one unit. An atomic operation is either fully done or not done, there is no intermediate state.  
       
       public class SharedStorage{
             private int a = 5;
             int increment(){
                    a++
               }
        }

Method, increment() in above class is one of the most trivial operations possible in Java (or any language). Is it atomic ? 
No, it is NOT.
The method is NOT indivisible as it consists of sub-operations (read the number and then increment it by one). So at any given time, it could be in either state (i.e. reading value or incrementing it by one). 

Importance of Atomic Operations

As we saw, even one of the simplest method is not atomic. The legitimate question is, is it really important?

If your application has a single thread of execution, it doesn't make any difference. Single threaded applications are sequential and they execute in the given order. So practically, the question of atomicity doesn't arise. 

Atomicity starts playing a role only if there is a possibility that an operation which modifies some shared data (or state) can be called by multiple threads at the same time. Assume that, two threads call increment() method of above class at the same time. What would be the value of a after completion of both threads? 

Final value of a could be 6 or 7. Let's understand this -

t1.start();
t2.start();

Even if thread, t2 starts after thread, t1 in your source code; JVM doesn't guarantee the same behavior at the time of execution. Sequential executions don't hold good anymore here. 


Java uses shared memory for communication between two threads. At the time of execution, both threads could read value of a at same time, hence, the incremented value will be 6. Or at the different time or on a different platform it a could have value as 7. This is a matter of concern!


In this post, I will be discussing how Java helps you to make operations atomic and also about some of the inbuilt atomic classes.

Important learning is; atomicity arises only when two or more threads can access a shared resource. 

Atomic Operations

We have seen earlier, even trivial operations like increment are not atomic in Java. Does it mean that no operation is atomic? Certainly NOT. Java ensures that some basic operations are always atomic:
  • Read and write for primitive variables (except long and double)
  • Read and write for reference variables 
  • Read and write for any variable (including long and double) and references declared as volatile
Java 7 specification clearly states that read or write to a non-volatile long or double is treated as two separate actions one for each 32 bit half.  Thus read and write on long/double are not atomic. Also, restriction of 32 bit is not applicable for references. So read and write on all references are atomic, irrespective of the size. 

Java also provides a second mechanism to make any shared attribute atomic by declaring it volatile [Java 7 Spec]. If a field is declared as volatile Java Memory Model ensures that all threads see a consistent value for the variable. 

Let's consider non-trivial operations which are commonplace in any normal application. We have discussed already, the increment method in SharedStorage class is NOT atomic.
How do you handle such operations in a concurrent environment?
How can we ensure that even if multiple threads invoke the methods, it remains in a consistent state?

Java provides another way to make an operation atomic.

 public class SharedStorage {  
      private int counter = 5;  //need to make it volatile
      public int getCounter(){  
           return counter;  
      }  
      public synchronized void increment(){  
           counter++;  
      }  
 }  

If you declare any method as synchronized, it means only one thread can invoke it at a time. This makes the operation atomic implicitly ( and makes thread-safe as well).

There is still a problem with above class ?
Assume that thread, t1 calls increment method, and in mean time another thread, t2 calls get method to read the value of counter. Which value will be read by thread t2 ; 5 or 6 ? Answer to this question is still uncertain; you can get any value.

Synchronizing increment method has solved one part of the problem. But it has not made the overall state of program consistent in a concurrent environment.

There are two alternatives to fix this :
  1. Either declare counter attribute as volatile. OR
  2. Synchronize the getCounter() method. 
You might question, why do we need to declare counter as volatile? We discussed earlier that read on primitives (except long and double) is atomic.
getCounter() method would have been atomic in the absence of synchronized increment method. We are forced to declare counter as volatile only due to synchronized increment method. To make a class fully thread safe all operations which are reading or modifying the state needs to be synchronized.

Java Provided Atomic APIs

Java has rich set of API for making life easier for programmers. So if you just need a atomic counter variable, you can use inbuilt class, instead of creating a new class altogether.
The java.util.concurrent.atomic package provides such classes. You can use AtomicInteger, AtomicLong etc as per your requirement instead of doing it from scratch.

No comments:

Post a Comment