Thursday, October 4, 2007

Volatile Variables In JAVA



Java Memory model defines the possible rules for threads and the expected behavior of multi-threaded programs so that programmers can design their programs accordingly. JSR-133 describes the semantics of threads, locks, volatile variables and data races.

Volatile variables are treated different from other variables. When you mark a variable as volatile, this warns the compiler to get fresh copies of the variable rather than caching the variable in the registers.

Volatile variables can be used to store shared variables at a lower cost than that of synchronization, but they have limitations. While writes to volatile variables are guaranteed to be immediately visible to other threads (because JVM does not allow threads to cache volatile variables), there is no way to render a read-modify-write sequence of operations atomic, meaning, for example, that a volatile variable cannot be used to reliably implement a mutex (mutual exclusion lock) or a counter.

Let's demonstrate this with a counter example. In the example a volatile counter variable will be incremented by multiple threads simultaneously. Below is the main class that holds the volatile variable and starts the threads.



01 package com.oksijen.concurrency.volatiletest;
02 
03 import java.util.concurrent.CountDownLatch;
04 
05 public class VolatileTest {
06 
07     public static volatile long counter = 0L;
08     private CountDownLatch allThreadsAreDone = null;;
09     private CountDownLatch allThreadsAreReady = null;
10     
11     public static void main(String[] args) {
12         VolatileTest test = new VolatileTest();
13         int numberOfThreads = 10;
14         int incrementCountPerThread = 10000;
15         CountDownLatch countDownLatch1 = new CountDownLatch(numberOfThreads);
16         test.setAllThreadsAreDone(countDownLatch1);
17         CountDownLatch countDownLatch2 = new CountDownLatch(numberOfThreads);
18         test.setAllThreadsAreReady(countDownLatch2);
19         test.start(numberOfThreads, incrementCountPerThread);
20         test.waitForThreadTermination();
21         System.out.println("Expected counter value : " + numberOfThreads * incrementCountPerThread);
22         System.out.println("Result : " + VolatileTest.counter);
23     }
24     /**
25      * prints out result after all threads finish execution
26      */
27     private void waitForThreadTermination() {
28         try {
29             System.out.println(Thread.currentThread().getName() " waiting for other threads...");
30             this.allThreadsAreDone.await();
31             System.out.println(Thread.currentThread().getName() " threads finished their work.");
32         catch (InterruptedException e) {
33             e.printStackTrace();
34         }
35     }
36     /**
37      @param numberOfThreads
38      @param incrementCount
39      */
40     private void start(int numberOfThreads, int incrementCount) {
41         for (int i=0; i<numberOfThreads; i++) {
42             MyThread thread = new MyThread(incrementCount, this.allThreadsAreDone, this.allThreadsAreReady);
43             thread.setName("MyThread_"+i);
44             thread.start();
45             this.allThreadsAreReady.countDown();
46         }
47     }
48     /**
49      * synchronized counter increment.
50      */
51     public static synchronized void incrementCounter(){
52         counter++;
53     }
54     /**
55      @param allThreadsAreDone the allThreadsAreDone to set
56      */
57     public void setAllThreadsAreDone(CountDownLatch allThreadsAreDone) {
58         this.allThreadsAreDone = allThreadsAreDone;
59     }
60     /**
61      @param allThreadsAreReady the allThreadsAreReady to set
62      */
63     public void setAllThreadsAreReady(CountDownLatch allThreadsAreReady) {
64         this.allThreadsAreReady = allThreadsAreReady;
65     }
66     
67 }


Here is our example thread source code:



01 package com.oksijen.concurrency.volatiletest;
02 
03 import java.util.concurrent.CountDownLatch;
04 
05 public class MyThread extends Thread {
06 
07     private int incrementCount = 0;    
08     private CountDownLatch done = null;
09     private CountDownLatch ready = null;
10     
11     public MyThread(int incrementCount, CountDownLatch done, CountDownLatch ready){
12         this.incrementCount = incrementCount;
13         this.done = done;
14         this.ready = ready;
15     }
16     
17     public void run() {
18         try {
19             System.out.println(Thread.currentThread().getName() " started.");
20             this.ready.await();
21             for (int i=0; i<this.incrementCount; i++) {
22                 VolatileTest.counter++;
23                 /*
24                  * if we want to increment the counter synchronously we use the
25                  * sync incrementCounter() method.
26                  
27                  * VolatileTest.incrementCounter();
28                  */  
29             }
30             System.out.println(Thread.currentThread().getName() " ended.");
31             this.done.countDown();            
32         catch (InterruptedException e) {
33             e.printStackTrace();
34         }
35     }
36 }


I used jdk1.5.0_12 to run this application. I used 10 threads, each increments the variable 10000 times. The result is as expected. Volatile keyword does not guarantie mutual exclusion. Here is the result:

Expected counter value : 100000
Result : 85751
As a result: Volatile variables are best for use when there is only one thread that makes writes (updates volatile variable) and N threads that make reads. Reader threads get the most recent value of the volatile parameter when they perform a read.

5 comments:

pveentjer said...

The reason why the example fails is that the increase operation is not atomic. It has nothing to do with the fact that the value is or is not volatile.

If you replace the volatile read/write by read/write used within a synchronized context, the application is going to suffer the same faith.

int value;

void synchronized void setValue(int value){
this.value = value;
}

void synchronized int getValue(){
return value;
}

void increase(){
setValue(getValue()+1)
}

Volatile serve 2 other very valuable goals (next to visibility).

1) They prevent reordering.
2) The make a save hand off possible (and this makes exchange of objects with visibility/reorderings problems possible between threads).

For more information about this subject I recommend "Java Concurrency in Practice" from Brian Goetz.

İlkin Ulaş BALKANAY said...

Thanks for your comment. The aim of this post is to prevent the misunderstanding of volatile variables. The increase operation is not atomic (as you mentioned) unless you use it in a synchronized block. Using volatile variables does not mean your increase operation will be atomic.
I used to use volatile variables as counters in my code in a multi-threaded environment. I decided to write this post when I realized that my volatile counter values are not incremented as I expected.

Anonymous said...

I am reading this article second time today, you have to be more careful with content leakers. If I will fount it again I will send you a link

Javin Paul said...

Nice tutorial. Java Developers often mistake volatile as replacement of synchronized keyword which is not true. synchronized provides highest level of thread-safety in Java. By the way I have also blogged as volatile keyword in Java , let me know how do you find it.

Archie Pavia said...

great post in your blog. It was very informative and useful. I would like to thank you for sharing your thoughts with us. -- CENTRAL PARK ZOO