Java: Volatile and Synchronized

Gist: a volatile variable isn't enough synchronization.


In Java, a "volatile" field is often presented as a weaker form of synchronization: a field that's specifically indicated to the compiler and runtime as "not-to-be-reordered", which, consequently, doesn't get cached; it is guaranteed to return the most recent write on the field to any threads accessing that field.

Because "most recent write" doesn't always imply "every write", using volatile as a form of synchronization could become problematic.

Consider the code below where the starting balance (represented by volatile member variable balance) is 0; two threads (the main thread, and another created (thread) change the balance to add 100 and 51000 respectively. After execution, one'd expect the final balance to be 51100 (100+51000) for a synchronized operation; however, that isn't what happens: on my machine, the final balance takes values like 51100, 51099, 51000 etc. See the output following the code.

import java.lang.Thread;
/* Over several executions of the program,
 a write to the  volatile variable "balance" 
 either by the main thread or by the created thread 
 is lost i.e. overwritten*/
public class ThreadTest
{
volatile int balance = 0;
public void deposit(int val)
{
balance = balance+val;
}

public void credit(int val)
{
if(balance>0)
balance = balance-val;
}
public void debit(int val) 
{
try
{
Thread internalThread = new Thread(new Runnable(){
@Override
public void run()
{
try
{
System.out.println("pre-change balance in second thread is: "+Integer.toString(balance));

deposit(val);
System.out.println("post-change balance in second thread is: "+Integer.toString(balance));
}
catch(Exception ioe)
{
System.out.println("error in debit thread run.");
}
}
});

internalThread.start();
}
catch(Exception ioe)
{
System.out.println("error in debit().");
}

}
public static void main(String[] args)  throws InterruptedException
{
System.out.println("Main Thread started");
ThreadTest tt = new ThreadTest();


System.out.println("Main Thread starting balance is: "+Integer.toString(tt.balance));

// alter balance from a new thread
tt.debit(100);

// alter balance from the main thread
for(int i = 1;i<=51000; i++)
{
tt.deposit(1);
}

System.out.println("Main Thread changed balance is: "+Integer.toString(tt.balance));
}
}


[my-pc java]$ java ThreadTest  
Main Thread started
Main Thread starting balance is: 0
pre-change balance in second thread is: 1420
post-change balance in second thread is: 2837
Main Thread changed balance is: 51000
[my-pc java]$ java ThreadTest        
Main Thread started
Main Thread starting balance is: 0
pre-change balance in second thread is: 935
post-change balance in second thread is: 1619
Main Thread changed balance is: 51100


In the same code, simply marking the method "deposit()" as synchronized ensures that the final balance is always 51100. Of course, the choice of using a synchronized method or a synchronized block is situational. 


You'll only receive email when they publish something new.

More from CAFEBABE
All posts