Java Theory and Practice: Correct Use of Volatile Variables

Source: Internet
Author: User

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 this visibility guarantee is not provided by the synchronization mechanism, the shared variables seen by the thread may be values before modification or inconsistent values, which will cause 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 that has an Invariants related to multiple variables (such as "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.

Conditions for correct use of volatile Variables

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.


List 1. Non-thread-safe value 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;
}
}

 

This method limits the range of state variables, so defining the lower and upper fields as volatile type does not fully implement the thread security of the class; thus, synchronization is still required. Otherwise, if the two threads use inconsistent values to execute setLower and setUpper at the same time, the range will be in an inconsistent state. For example, if the initial status is (0, 5), thread A calls setLower (4) at the same time and thread B calls setUpper (3 ), obviously, the values of the two operations do not meet the conditions, so both threads will pass the check for the non-variant protection so that the final range value is (4, 3) -- an invalid value. For other operations on the scope, we need to make the setLower () and setUpper () Operations atomic -- and defining the 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 the same as the non-volatile read operation. 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 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: Status flag

The standard usage of volatile variables may only use a Boolean status flag to indicate an important one-time event, such as initialization or request downtime.

Many applications contain a control structure in the form of "execute some work when you are not ready to stop the program", as shown in Listing 2:


Listing 2. Using the volatile variable as a status flag

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. (It may be called from the JMX listener, the Operation listener in the GUI event thread, through RMI, through a Web service, etc ). 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 safe publication)

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.


Listing 3. Using the volatile variable for one-time secure Publishing

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.


Listing 4. Using the volatile variable to publish 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;
}
}

 

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 attributes 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:


Listing 5. Person objects in volatile bean Mode

@ 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;
}
}

 

Volatile Advanced Mode

The models described in the previous sections cover most of the basic use cases. Using volatile in these models is very useful and simple. This section describes a more advanced mode in which volatile provides performance or scalability advantages.

The advanced mode of volatile applications is very fragile. Therefore, you must carefully prove the assumptions and these patterns are strictly encapsulated, because even small changes can damage your code! Similarly, the reason for using a more advanced volatile use case is that it can improve performance and ensure that this performance benefit is truly determined before the advanced mode is applied. You need to weigh these models and discard readability or maintainability in exchange for possible performance gains-if you do not need to improve performance (or you cannot prove that you need it through a strict test program ), this is probably a bad transaction, because you are likely to lose more than you lose, and the value you get is lower than the value you give up.

Mode #5: read-write lock policies with low overhead

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 overhead of the read path only involves volatile read operations, which is usually better than the overhead of a non-competitive lock acquisition.


Listing 6. Using volatile and synchronized together to implement "low-overhead read-write locks"

@ 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 ++;
}
}

 

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 read operations to ensure the visibility of the current value. Therefore, you can use the lock to perform all changed operations and use volatile for 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 use volatile instead of synchronized to simplify the code. However, code using volatile is often more error-prone than code using locks. The pattern described in this article covers some of the most common use cases that can replace synchronized with volatile. Following these patterns (note that they must not exceed their limits) helps you securely implement most use cases and use volatile variables for better performance.

This article is from "wangqiaowqo"
 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.