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.