///Use Synchronized or Volatile when Accessing Shared Variables

Use Synchronized or Volatile when Accessing Shared Variables

Use synchronized or volatile when accessing shared variables

Editor’s note: The following article is an excerpt from the book "Practical Java" published by Addison-Wesley. You can order this book from Borders.com.

When variables are shared between threads, they must always be accessed properly in order to ensure that correct and valid values are manipulated. The JVM is guaranteed to treat reads and writes of data of 32 bits or less as atomic. This might lead some programmers to believe that access to shared variables does not need to be synchronized or the variables declared volatile . Consider this code:



class RealTimeClock
{
private int clkID;
private long clockTime;

public int clockID()
{
return clkID;
}
public void setClockID(int id)
{
clkID = id;
}

public long time()
{
return clockTime;
}
public void setTime(long t)
{
clockTime = t;
}
//...
}

Now contemplate an implementation that uses the previous code. It might create an object of the RealTimeClock class and two threads. It then could call the methods of this class from the two threads.

The variables clkID and clockTime are stored in main memory. However, the Java language allows threads to keep private working copies of these variables. This enables a more efficient execution of the two threads. For example, when each thread reads and writes these variables, they can do so on the private working copies instead of accessing the variables from main memory. The private working copies are reconciled with main memory only at specific synchronization points.

The clockID and setClockID methods perform only a read and a write, respectively, on data of type int. Therefore, the operation of these methods is automatically atomic. However, given that the clkID variable could be stored in private working memory for each thread, consider the following possible sequence of events:

  1. Thread 1 calls the setClockID method, passing a value of 5.
  2. Thread 2 calls the setClockID method, passing a value of 10.
  3. Thread 1 calls the clockID method, which returns the value 5.

This sequence of events is possible because the clkID variable is not guaranteed to be reconciled with main memory. During step 1, thread 1 places the value 5 in its working memory. When step 2 executes, thread 2 places the value 10 in its working memory. When step 3 executes, thread 1 reads the value from its working memory and returns 5 . At no point are the values reconciled with main memory.

There are two ways to fix this problem.

  1. Access the clkID variable from a synchronized method or block.
  2. Declare the clkID variable volatile.

Either solution requires the clkID variable to be reconciled with main memory. Accessing the clkID variable from synchronized method or block does not allow that code to execute concurrently, but it does guarantee that the clkID variable in main memory is updated appropriately. Main memory is updated when the object lock is obtained before the protected code executes, and then when the lock is released after the protected code executes.

Declaring the clkID variable volatile allows the code to execute concurrently and also guarantees that the private working copy of the clkID variable is reconciled with main memory. This reconciliation, however, occurs each time the variable is accessed.

Implementations of JVMs are encouraged to treat 64-bit operations as atomic but are not required to do so. This is because some popular microprocessors currently do not provide efficient atomic memory transactions on 64-bit values.

Also consider the time and setTime methods that operate on a variable of type long. These methods can exhibit the same problem described previously. They also have an additional problem. Data of type long is typically represented by 64 bits spread across two 32-bit words. An implementation of the JVM might treat the operation on a 64-bit value as atomic, but most JVM implementations today do not, instead treating such operations as two distinct 32-bit operations. Consider the following possible sequence of events with the clockTime instance variable:

  1. Thread 1 calls the time method.
  2. Thread 1 begins to read the clockTime instance variable and reads the first 32 bits.
  3. Thread 1 is preempted by thread 2.
  4. Thread 2 calls the setTime method. The setTime method performs two writes of 32 bits each to the clockTime instance variable, replacing both 32-bit values with different values.
  5. Thread 2 is preempted by thread 1.
  6. Thread 1 reads the second 32 bits of the clockTime instance variable and returns the result.

According to this sequence of events, the value returned by the time method is made up of the first 32 bits of the old value of the clockTime instance variable, and the second 32 bits of the new value of the same instance variable. The value returned is not correct. This is because of the multiple reads and writes necessary for 64-bit data in the JVM. The private working memory and main memory issue discussed earlier is also a problem. You have the same two options to fix this problem: synchronize access to the clockTime variable or declare it volatile.

In summary, it is important to understand that atomic operations do not automatically mean thread-safe operations. In addition, whenever multiple threads share variables it is important that they are accessed in a synchronized method or block, or are declared with the volatile keyword. This ensures that the variables are properly reconciled with main memory, thereby guaranteeing correct values at all times.

Whether you use volatile or synchronized depends on several factors. If concurrency is important and you are not updating many variables, consider using volatile. If you are updating many variables, however, using volatile might be slower than using synchronization. Remember that when variables are declared volatile, they are reconciled with main memory on every access. By contrast, when synchronized is used, the variables are reconciled with main memory only when the lock is obtained and when the lock is released.

Consider using synchronized if you are updating many variables and do not want the cost of reconciling each of them with main memory on every access, or you want to eliminate concurrency for another reason.

The following table summarizes the differences between the synchronized and volatile keywords.

Differences between volatile and synchronized

Technique Advantages Disadvantages
synchronized Private working memory is reconciled with main memory when the lock is obtained and when the lock is released. Eliminates concurrency.
volatile Allows concurrency. Private working memory is reconciled with main memory on each variable access.
2010-05-26T17:17:35+00:00 March 9th, 2003|Java|0 Comments

About the Author:

Leave A Comment