Source: http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
Introduction:The Java language includes two internal synchronization mechanisms: Synchronous block (or method) and volatile variables. Both mechanisms are proposed to achieve code thread security. Among them, the synchronization of volatile variables is poor (but sometimes it is simpler and has lower overhead), and its usage is more error-prone. In this phase of Java theory and practice, Brian Goetz will introduce several modes for correct use of volatile variables, and give some suggestions on its applicability limitations.
Volatile variables in Java can be considered as "synchronized with a lighter degree". Compared with synchronized blocks, volatile variables require less encoding and less runtime overhead, but what it can achieve is only part of synchronized. This article introduces several effective modes for Using volatile variables, and emphasizes the situations where volatile variables are not suitable for use.
The lock provides two main features: mutual exclusion and visibility ). Mutex allows only one thread to hold a specific lock at a time. Therefore, you can use this feature to implement a coordinated access protocol for shared data. In this way, only one thread can use the shared data at a time. Visibility is more complex. It must ensure that changes made to the shared data before the lock is released are visible to another thread that subsequently acquires the lock.
-- If there is no such visibility guarantee provided by the synchronization mechanism, the shared variables seen by the thread may be the values before the modification or inconsistent values, which will lead to many serious problems.
Volatile variable
Volatile variables have the visibility of synchronized, but do not have atomic properties. This means that the thread can automatically discover the latest value of the volatile variable. Volatile variables can be used to provide thread security, but can only be used in a very limited set of Use Cases: There is no constraint between multiple variables or between the current value of a variable and the modified value of a variable. Therefore, using volatile alone is not enough to implement counters, mutex locks, or any class (such
"Start <= end ").
For simplicity or scalability, you may prefer to use volatile variables instead of locks. Some usage (idiom) is easier to code and read when volatile variables are used instead of locks. In addition, the volatile variable does not cause thread blocking like a lock, so it rarely causes scalability problems. In some cases, if the read operation is far greater than the write operation, the volatile variable can also provide performance advantages over the lock.
Correct conditions for using the volatile variable:
You can only replace the lock with the volatile variable in a limited number of cases. To enable the volatile variable to provide ideal thread security, the following conditions must be met simultaneously:
- Write operations on variables do not depend on the current value.
- This variable is not included in the variant with other variables.
In fact, these conditions indicate that the valid values that can be written into the volatile variable are independent of the State of any program, including the current state of the variable.
The limitation of the first condition makes the volatile variable not used as a thread security counter. Although the incremental operation (x ++) Looks like a separate operation, it is actually a combination of read-Modify-write operation sequences and must be performed in an atomic manner, volatile cannot provide the required atomic features. The correct operation requires that the value of X remains unchanged during the operation, but the volatile variable cannot implement this. (However, if you adjust the value to write only from a single thread, you can ignore the first condition .)
Most programming scenarios conflict with one of these two conditions, making volatile variables not as universally applicable to thread security as synchronized. Listing 1 shows a non-thread-safe value range class. It contains a non-variant-the lower bound is always less than or equal to the upper bound.
@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; }}
This method limits the range of state variables, solower
The definition of the upper field as the volatile type does not fully implement the thread security of the class; therefore, synchronization still needs to be used. Otherwise, if two threads use inconsistent values for execution at the same timesetLower
AndsetUpper
Otherwise, the range will be in an inconsistent state. For example, if the initial status is(0,
5)
, Thread a calls at the same timesetLower(4)
And thread B callssetUpper(3)
Obviously, the values of the two operations do not meet the conditions, so both threads will pass the check to protect the variant, so that the final range value is(4,
3)
-- An invalid value. For other operations on the scope, we needsetLower()
AndsetUpper()
Operation atomicity
-- Defining a field as the volatile type cannot achieve this goal.
Performance Considerations
The main reason for using the volatile variable is its simplicity: in some cases, using the volatile variable is much easier than using the corresponding lock. The secondary reason for using the volatile variable is its performance: in some cases, the volatile variable synchronization mechanism has better performance than the lock.
It is difficult to make accurate and comprehensive comments, such as "X is always faster than Y", especially for internal JVM operations. (For example, in some cases, the VM may be able to completely delete the lock mechanism, which makes it difficult to abstract the overhead of volatile and synchronized .) That is to say, in most of the current processor architectures, the volatile read operation overhead is very low-almost non-volatile
Read operations are the same. Volatile write operations have more overhead than non-volatile write operations, because to ensure visibility, You need to implement memory definition (memory fence). Even so, the total cost of volatile is still lower than the lock acquisition.
Volatile operations do not cause congestion like locks. Therefore, volatile provides some scalable features that are better than locks when volatile can be safely used. If the number of read operations far exceeds the write operation, compared with the lock, the volatile variable can usually reduce the performance overhead of synchronization.
Correct volatile variable usage mode
Many concurrency experts actually tend to guide users away from volatile variables because using them is more error-prone than using locks. However, if you follow well-defined patterns with caution, you can safely use the volatile variable in many scenarios. Always remember the restrictions on Using volatile-use volatile only when the status is truly independent from other content in the program-this rule can avoid extending these patterns to insecure use cases.
Mode-1 flag status
volatile boolean shutdownRequested;...public void shutdown() { shutdownRequested = true; }public void doWork() { while (!shutdownRequested) { // do stuff }}
It is very likely that the shutdown () method will be called from outside the loop-that is, in another thread-therefore, you need to execute some synchronization to ensure correct visibility of the shutdownrequested variable. (May
JMX listeners, Operation listeners in Gui event threads, calls through RMI, and a web service ). However, writing a loop using synchronized blocks is much more difficult than using the volatile status flag shown in Listing 2. Because volatile simplifies encoding and the status flag does not depend on any other State in the program, volatile is ideal for this scenario.
One common feature of this type of state flag is that there is usually only one state conversion; The shutdownrequested flag is converted from false to true, and then the program stops. This mode can be extended to the status flag of the back-and-forth conversion, but can be extended only when the conversion cycle is not noticed (from false to true, then to false ). In addition, some atomic state conversion mechanisms, such as atomic variables, are required.
Mode-2 one-time security release
Lack of synchronization makes visibility impossible, making it more difficult to determine when to write object references instead of primitive values. In the absence of synchronization, the updated value referenced by an object (written by another thread) may coexist with the old value in the object state. (This is the root cause of the famous double-checked-locking problem, where the object reference is read without synchronization, the problem is that you may see an updated reference, but you will still see an incomplete object through this reference ).
One technique for implementing secure publishing objects is to define object reference as volatile type. Listing 3 shows an example in which the background thread loads some data from the database during the startup phase. When other code can exploit the data, it will check whether the data has been published before use.
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); } }}
If theflooble reference is not of the volatile type, when the code in dowork () removes the reference to theflooble, it will get an incomplete flooble.
A necessary condition for this mode is that the published object must be thread-safe, or a valid immutable object (valid immutable means that the object state will never be modified after it is released ). Volatile type references can ensure the visibility of objects in the form of release. However, if the object state changes after release, additional synchronization is required.
Mode-3 independent observation
Another simple mode for Safely Using volatile is to regularly "publish" the observed results for internal use of the program. For example, assume that an environmental sensor can feel the ambient temperature. A background thread may read the sensor every several seconds and update the volatile variable containing the current document. Other threads can then read this variable to view the latest temperature value at any time.
Another application that uses this mode is to collect program statistics. Listing 4 shows how the identity authentication mechanism remembers the name of the last user logged on. The lastuser reference is repeatedly used to publish values for other parts of the program.
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; }}
This mode is an extension of the previous mode. Publishing a value is used elsewhere in the program, but unlike publishing a one-time event, this is a series of independent events. This mode requires that the published value be valid and unchangeable-that is, the status of the value is not changed after the release. The code that uses this value must be clear that this value may change at any time.
Mode-4 volatile bean Mode
The volatile bean mode applies to the framework that uses JavaBeans as the "honor structure. In the volatile bean mode, JavaBean is used as a group of containers with independent attributes of getter and/or setter methods. The basic principle of the volatile bean mode is that many frameworks provide containers for the holders of variable data (such as httpsession), but the objects in these containers must be thread-safe.
In the volatile bean mode, all data members of the JavaBean are of the volatile type, and the getter and setter methods must be very common-apart from obtaining or setting corresponding attributes, they cannot contain any logic. In addition, for data members referenced by an object, the referenced object must be valid and unchangeable. (This will disable the attribute with array values, because when the array reference is declared as volatile, only the reference instead of the array itself has
Volatile semantics ). For any volatile variable, the unchanged type or constraints cannot contain the JavaBean attribute. The example in listing 5 shows the JavaBean following the volatile bean mode:
@ThreadSafepublic 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; }}
Volatile Advanced Mode
So far, you have learned that the volatile function is not enough to implement counters. Because ++ X is actually a simple combination of three operations (read, add, and storage), if multiple threads try to perform incremental operations on the Volatile counter at the same time, the update value may be lost.
However, if read operations far exceed write operations, you can use internal locks and volatile variables to reduce the overhead of Public Code paths. The thread-safe counter shown in Listing 6 uses synchronized to ensure that incremental operations are atomic and volatile to ensure visibility of the current result. If the update frequency is not frequent, this method can achieve better performance, because the read path overhead only involves volatile.
Read operations, which are generally superior to the overhead of a non-competitive lock.
@ThreadSafepublic 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++; }}
This technology is called a "low-overhead read-write lock" because you use different synchronization mechanisms for read/write operations. Because the write operation in this example violates the first condition for Using volatile, you cannot use volatile to securely implement the counter-you must use the lock. However, you can use volatile in the read operation to ensure the visibility of the current value, so you can use the lock to perform all the changed operations and use
Volatile performs read-only operations. The lock only allows one thread to access the value at a time, and volatile allows multiple threads to perform read operations. Therefore, when volatile is used to ensure the read code path, it is more shared than using the lock to execute all code paths-just like a read-write operation. However, keep in mind the weakness of this pattern: if the most basic application of this pattern is exceeded, it will become very difficult to combine these two competing synchronization mechanisms.
Conclusion
Compared with the lock, the volatile variable is a very simple but fragile synchronization mechanism that will provide better performance and scalability than the lock in some cases. If you strictly follow the volatile usage conditions-that is, variables are truly independent from other variables and their previous values-in some cases, you can usevolatile
Replacesynchronized
To simplify the code. Howevervolatile
The code is often more error-prone than the code that uses the lock. The mode described in this article covers the availablevolatile
Replacesynchronized
The most common use cases. Following these modes (note that you do not need to exceed the limits) can help you securely implement most use cases.
The volatile variable provides better performance.