Java theory and Practice

Use Volatile variables correctly

Guide to using volatile Variables

Series contents:

This is part of the series:Java theory and Practice

Volatile variables in the Java language can be thought of as a form of “lesser synchronized”; Volatile variables require less coding and have less runtime overhead than synchronized blocks, but they do only a fraction of what synchronized does. This article describes several patterns for using volatile variables effectively and highlights several situations where volatile variables are not appropriate.

Locks provide two main features: mutual exclusion and visibility. Mutual exclusion means that only one thread at a time is allowed to hold a particular lock, so this feature can be used to implement a coordinated access protocol to shared data so that only one thread at a time can use the shared data. Visibility is more complicated, it must ensure that the lock is released before making changes to Shared data for later another thread to acquire the lock is visible – if there is no synchronization mechanism provides the visibility that thread to see the value of Shared variables can be modified before or inconsistent values, it will cause many serious problems.

Volatile variables

Volatile variables have the visibility of synchronized, but not atomic properties. This means that threads can automatically discover the latest values of volatile variables. Volatile variables can be used to provide thread-safety, but only for a very limited set of use cases: there are no constraints between variables or between the current and modified values of a variable. Thus, volatile alone is not sufficient to implement counters, mutexes, or any class with Invariants associated with more than one variable (such as “start <=end”).

For simplicity or scalability, you might prefer to use volatile variables instead of locks. When volatile variables are used instead of locks, certain idioms are easier to code and read. In addition, volatile variables do not block threads in the same way locks do, and therefore rarely cause scalability problems. In some cases, volatile variables can also provide a performance advantage over locks if reads far outnumber writes.

Conditions for the correct use of volatile variables

You can use volatile variables instead of locks only in a limited number of cases. For volatile variables to provide ideal thread-safety, both conditions must be met:

  • Writes to variables do not depend on the current value.
  • This variable is not contained in an invariant with other variables.

In effect, these conditions indicate that the valid values that can be written to volatile variables are independent of the state of any program, including the current state of the variables.

The first condition restricts volatile variables from being used as thread-safe counters. Although the delta operation (x++) looks like a single operation, it is actually a combination of a sequence of read-modify-write operations that must be performed atomically, and volatile does not provide the required atomic properties. The correct operation requires that the value of x remain constant for the duration of the operation, which volatile variables do not. (However, if you adjust the value to write only from a single thread, you can ignore the first condition.)

Most programming situations run afoul of one of these two conditions, making volatile variables less generally applicable to thread-safety than synchronized. Listing 1 shows a non-thread-safe numeric range class. It contains the invariant that the lower bound is always less than or equal to the upper bound.

Listing 1. A non-thread-safe numeric range class
@NotThreadSafe public class NumberRange { private int lower, upper; public int getLower() { return lower; } public int getUpper() { return upper; } public void setLower(int value) { if (value > upper) throw new IllegalArgumentException(...) ; lower = value; } public void setUpper(int value) { if (value < lower) throw new IllegalArgumentException(...) ; upper = value; }}Copy the code

This approach limits the scope of state variables, so making the lower and upper fields volatile is not sufficient for class thread-safety; So you still need to use synchronization. Otherwise, if you happen to have two threads executing setLower and setUpper with inconsistent values at the same time, you will leave the scope in an inconsistent state. For example, if the initial state is (0, 5), and thread A calls setLower(4) and thread B calls setUpper(3) at the same time, both threads will pass the check used to protect the invariant. Such that the final range value is (4, 3) — an invalid value. For the other operations on scopes, we need to atomize the setLower() and setUpper() operations — and making fields volatile won’t do that.

Performance considerations

The main reason for using volatile variables is simplicity: in some cases, using volatile variables is much simpler than using the corresponding locks. A secondary reason for using volatile variables is performance: in some cases, volatile variable synchronization is better than locking.

It is difficult to make an accurate, comprehensive assessment, such as “X is always faster than Y,” especially for operations within the JVM. (For example, in some cases the VM may be able to remove the locking mechanism entirely, making it difficult to compare the overhead of volatile and synchronized in an abstract way.) That is, on most processor architectures today, volatile reads are very cheap — almost as expensive as non-volatile reads. Volatile writes are much more expensive than non-volatile writes because the Memory Fence is required to ensure visibility, but even so, the total cost of volatile writing is lower than lock acquisition.

Volatile operations do not block like locks, so volatile can provide some scalability advantages over locks when they can be used safely. If reads far outnumber writes, volatile variables can often reduce the performance cost of synchronization compared to locking.

Use volatile patterns correctly

Many concurrency experts actually steer users away from volatile variables because they are more error-prone than locks. However, if you carefully follow well-defined patterns, volatile variables can be used safely in many situations. Always keep in mind the limitations of using volatile — volatile can only be used if the state is truly independent of the rest of the program — and this rule prevents the extension of these patterns to insecure use cases.

Pattern #1: Status flags

Perhaps the canonical use of volatile variables is simply the use of a Boolean status flag to indicate that an important one-time event, such as initialization completion or shutdown request, has occurred.

Many applications include a control structure in the form of “do some work when you’re not ready to stop the program,” as shown in Listing 2:

Listing 2. Using volatile variables as status flags
volatile boolean shutdownRequested; . public void shutdown() { shutdownRequested = true; } public void doWork() { while (! shutdownRequested) { // do stuff } }Copy the code

It is likely that the shutdown() method will be called from outside the loop — that is, in another thread — so some kind of synchronization will need to be performed to ensure that the visibility of the shutdownRequested variable is properly implemented. (It might be called from a JMX listener, an action listener in a GUI event thread, through RMI, through a Web service, and so on). However, writing a loop using synchronized blocks is a lot more cumbersome than writing a loop using the volatile status flags shown in Listing 2. Because volatile simplifies coding and status flags do not depend on any other state in the program, volatile is a good place to use it.

A common feature of this type of state markup is that there is usually only one state transition; The shutdownRequested flag is converted from false to true, and the program stops. This pattern can be extended to state flags that transition back and forth, but only if the transition cycle is undetected (from false to true, to false again). In addition, certain atomic state transition mechanisms, such as atomic variables, are required.

Pattern #2: One-off Safe Publication

Lack of synchronization leads to an inability to achieve visibility, which makes it more difficult to determine when to write object references instead of primitive values. In the absence of synchronization, you might encounter an updated value referenced by an object (written by another thread) and an old value of the object’s state. (This is the root cause of the famous double-checked locking problem, where object references are read without synchronization, and the problem is that you may see an updated reference, but still see an incompletely constructed object through that reference).

One technique for safely publishing objects is to make object references volatile. Listing 3 shows an example where a background thread loads some data from a database during startup. When other code is able to take advantage of this data, it checks to see if it has ever been published before before using it.

Listing 3. Using volatile variables for one-time safe publication
public class BackgroundFloobleLoader { public volatile Flooble theFlooble; public void initInBackground() { // do lots of stuff theFlooble = new Flooble(); // this is the only write to theFlooble } } public class SomeOtherClass { public void doWork() { while (true) { // do some stuff... // use the Flooble, but only if it is ready if (floobleLoader.theFlooble ! = null) doSomething(floobleLoader.theFlooble); }}}Copy the code

If theFlooble reference is not of volatile type, the code in doWork() will get an incomplete constructed Flooble when it unreferences theFlooble.

A requirement of this pattern is that the published object must be thread-safe or effectively immutable (effectively immutable means that the state of the object is never changed after publication). Volatile references ensure visibility of the object’s publication form, but additional synchronization is required if the object’s state will change after publication.

Pattern #3: Independent Observation

Another simple pattern for using volatile safely is to periodically “publish” observations for internal use in the program. For example, suppose you have an ambient sensor that senses the ambient temperature. A background thread might read the sensor every few seconds and update the volatile variable containing the current document. Other threads can then read this variable and see the latest temperature value at any time.

Another application that uses this pattern is to collect program statistics. Listing 4 shows how the authentication mechanism remembers the name of the user who last logged in. The lastUser reference is used repeatedly to publish values for use by other parts of the program.

Listing 4. Using volatile variables for the publication of multiple independent observations
public class UserManager { public volatile String lastUser; public boolean authenticate(String user, String password) { boolean valid = passwordIsValid(user, password); if (valid) { User u = new User(); activeUsers.add(u); lastUser = user; } return valid; }}Copy the code

This pattern is an extension of the previous pattern; Publishing a value for use elsewhere in the program, but rather than publishing a one-time event, this is a series of independent events. This pattern requires that published values be effectively immutable — that is, the state of the value does not change after publication. Code that uses this value needs to be aware that it can change at any time.

Pattern #4: “Volatile bean” pattern

The Volatile Bean pattern applies to frameworks that use JavaBeans as honor structures. In the Volatile bean pattern, Javabeans are used as containers for a set of independent properties with getters and/or setter methods. The rationale for the Volatile bean pattern is that many frameworks provide containers for holders of volatile data, such as HttpSession, but the objects placed in these containers must be thread-safe.

In the Volatile Bean pattern, all data members of javabeans are of volatile type, and getter and setter methods must be plain — no logic other than getting or setting the corresponding property. In addition, for the data members referenced by the object, the referenced object must be effectively immutable. This disallows properties with array values, because when an array reference is declared volatile, only the reference, not the array itself, has volatile semantics. For any volatile variable, invariants or constraints must not contain JavaBean attributes. The example in Listing 5 shows a JavaBean that complies with the Volatile bean pattern:

Listing 5. The Person object that complies with the Volatile bean schema
@ThreadSafe public class Person { private volatile String firstName; private volatile String lastName; private volatile int age; public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public int getAge() { return age; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setAge(int age) { this.age = age; }}Copy the code

The advanced mode for volatile

The patterns described in the previous sections cover most of the basic use cases, and it is useful and easy to use volatile in these patterns. This section introduces a more advanced pattern in which volatile provides performance or scalability benefits.

The advanced mode for volatile applications is vulnerable. Therefore, the assumptions must be carefully proven, and the patterns are tightly encapsulated, because even very small changes can break your code! Again, the reason for using a more advanced volatile use case is that it improves performance, ensuring that you really know that you need to realize the performance benefit before you start applying the advanced pattern. Need to weigh these patterns, abandon the readability and maintainability for possible performance gains – if you don’t need to improve performance (or are not able to pass a strict test program that you need it), then it is likely to be a bad deal, because you’ll likely do more harm than good, for what value is lower than to give up things.

Pattern #5: Low overhead read-write locking strategy

By now, you should know that volatile is not powerful enough to implement counters. Because ++x is really a simple combination of three operations (read, add, and store), its updated value can be lost if multiple threads happen to attempt to increment a volatile counter at the same time.

However, if reads far outstrip writes, you can use a combination of internal locks and volatile variables to reduce the overhead of common code paths. The thread-safe counter shown in Listing 6 uses synchronized to ensure that increments are atomic and volatile to ensure visibility of the current result. If updates are infrequent, this approach achieves better performance because the read path’s overhead involves only volatile reads, which is generally superior to the overhead of an uncontested lock acquisition.

Listing 6. Low Cost read-write locking using volatile and synchronized
@ThreadSafe public class CheesyCounter { // Employs the cheap read-write lock trick // All mutative operations MUST be done with the 'this' lock held @GuardedBy("this") private volatile int value; public int getValue() { return value; } public synchronized int increment() { return value++; }}Copy the code

This technique is called “low-cost read-write locking” because you use a different synchronization mechanism for reading and writing. Because the writes in this example violate the first condition for using volatile, you cannot safely implement counters using volatile — you must use locks. However, you can use volatile in read operations to ensure visibility of the current value, so you can use locks for all changes and volatile for read-only operations. Where locks allow only one thread to access values at a time, volatile allows multiple threads to perform reads, so using volatile to guarantee a read path is more shared than using locks to execute the entire code path — just as with read-write operations. However, keep in mind the weakness of this pattern: combining these two competing synchronization mechanisms becomes very difficult if you go beyond the most basic application of the pattern.

conclusion

Volatile variables are a very simple but fragile synchronization mechanism compared to locking, and in some cases provide better performance and scalability than locking. If the conditions for using volatile are strictly adhered to — that is, variables are truly independent of other variables and their previous values — it is possible in some cases to simplify code by using volatile instead of synchronized. However, code that uses volatile is often more error-prone than code that uses locks. The patterns described in this article cover some of the most common use cases where volatile can replace synchronized. Following these patterns (being careful not to exceed their respective limits when used) can help you safely implement most use cases for better performance with volatile variables.

On the topic

  • You can see the original English version of this article on the developerWorks world site.
  • Java Concurrency in Practice: A How-to manual for developing concurrent applications using Java code. It covers building and writing thread-safe classes and programs, avoiding performance impacts, managing performance, and testing concurrent applications.
  • Popular Atoms: Introduces the new atomic variable class in Java 5.0, which extends volatile variables to support atomic state transitions.
  • Introduction to non-blocking algorithms: Describes how to implement concurrent algorithms using atomic variables instead of locks.
  • Volatiles: Get more information about volatile variables from Wikipedia.
  • Java Technology zone: Provides hundreds of articles on all aspects of Java programming.