Java Theory and Practice: using Volatile variables correctly

Source: Internet
Author: User
Tags volatile

Original source: http://www.ibm.com/developerworks/cn/java/j-jtp06197.html

Guidelines for using volatile variables

The Java™ language contains two intrinsic synchronization mechanisms: synchronous blocks (or methods) and volatile variables. Both of these mechanisms are proposed to achieve the security of code threads. Where volatile variables are less synchronous (but sometimes simpler and less expensive), and their use is more error-prone. In this period of Java theory and Practice , Brian Goetz will introduce several patterns of correct use of volatile variables, and put forward some suggestions for their applicability limitations.

See more in this series

Volatile variables in the Java language can be thought of as "light", and synchronized synchronized volatile variables require less coding and run-time overhead than blocks, but only part of the functionality that they can implement synchronized . This article describes several patterns for using volatile variables effectively, and highlights several scenarios where volatile variables are not appropriate.

The lock provides two main features: Mutual exclusion (mutual exclusion) and visibility (visibility). Mutual exclusion allows only one thread to hold a particular lock at a time, so you can use this attribute to implement a coordinated access protocol to shared data so that only one thread can use that shared data at a time. Visibility is more complex, and it must ensure that the changes made to the shared data before the lock is released are visible to the other thread that subsequently acquired the lock-if there is no guarantee of this visibility provided by the synchronization mechanism, the shared variables that the thread sees may be pre-modified or inconsistent values, which can cause many serious problems.

Volatile variable

Volatile variables have synchronized the visibility characteristics, but do not have 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 can only be applied to 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. Therefore, using volatile alone is not sufficient to implement counters, mutexes, or any class that has invariant (invariants) associated with multiple variables (for example, "Start <=end").

For simplicity or scalability, you might prefer to use volatile variables instead of locks. Some idioms (idiom) are easier to encode and read when using volatile variables rather than locks. In addition, volatile variables do not cause threads to block like locks, and therefore are less likely to cause scalability problems. In some cases, if the read operation is much larger than the write operation, the volatile variable can also provide a performance advantage over the lock.

Conditions for correct use of volatile variables

You can use volatile variables instead of locks in a limited number of situations. For the volatile variable to provide ideal thread safety, the following two conditions must be met:

    • The write operation on the variable does not depend on the current value.
    • The variable is not contained in an invariant that has other variables.

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

The first condition restricts the volatile variable from being used as a thread-safe counter. Although an incremental operation ( x++ ) looks like a separate operation, it is actually a combination of a read-modify-write sequence of operations that must be performed atomically, and volatile does not provide the necessary atomic properties. Implementing the correct operation requires that x the values remain constant during the operation, and the volatile variable cannot achieve this. (However, if you adjust the value to write only from a single thread, the first condition can be ignored.) )

Most programming scenarios conflict with one of these two conditions, making volatile variables not as synchronized universally applicable to thread safety. Listing 1 shows a non-thread-safe numeric range class. It contains an invariant-the nether is always less than or equal to the upper bound.

Listing 1. Non-thread-safe numeric range classes
@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 approach restricts the state variables of the scope, so lower defining the and upper fields as volatile types does not fully implement the thread safety of the class, and thus still requires synchronization. Otherwise, if two threads are executed at the same time with inconsistent values setLower setUpper , then the scope will be in an inconsistent state. For example, if the initial state is, at the (0, 5) same time, thread A is called setLower(4) and thread B is called setUpper(3) , it is clear that the values crossed by these two operations are not eligible, then two threads will pass the check to protect the invariant so that the final range value is (4, 3) -an invalid value. As for other operations on the scope, we need to make setLower() and setUpper() operate atomically--and it is not possible to define a field as a volatile type.

Performance considerations

The main reason for using volatile variables is its simplicity: in some cases, it is much simpler to use volatile variables than to use the corresponding locks. The secondary cause of using volatile variables is its performance: in some cases, the performance of the volatile variable synchronization mechanism is better than the lock.

It is difficult to make accurate, comprehensive evaluations, such as "X is always faster than Y", especially for internal operations within the JVM. (for example, in some cases the VM might be able to completely remove the lock mechanism, which makes it difficult to compare volatile and synchronized cost abstractly.) That is, the volatile read operation overhead is very low on most of the current processor architectures-almost the same as non-volatile read operations. The cost of volatile writes is much more than non-volatile writes, because to ensure that visibility requires memory Fence, even then, the total cost of volatile is still lower than lock fetching.

Volatile operations do not clog like locks, so volatile can provide some of the scalability characteristics that are better than locks in situations where volatile is safe to use. If the number of read operations is much greater than the write operation, volatile variables can often reduce the performance overhead of synchronization compared to locks.

Correct use of volatile patterns

Many concurrency experts in fact tend to steer users away from volatile variables, because using them is more error-prone than using locks. However, if you carefully follow some well-defined patterns, you can safely use volatile variables in many situations. Always keep in mind the limitations of using volatile--a rule that uses volatile--only if the state is truly independent of other content within the program avoids extending these patterns to unsafe use cases.

Mode #1: Status flag

Perhaps the canonical use of a volatile variable is to use only a Boolean status flag that indicates that an important one-time event has occurred, such as completion of initialization or request for downtime.

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

Listing 2. Use the volatile variable as a status flag
Volatile Boolean shutdownrequested;...public void shutdown () {shutdownrequested = true;} public void DoWork () {when     (!shutdownrequested) {         //do Stuff    }}

It is likely that the method is called from outside the loop- shutdown() that is, in another thread-and therefore some synchronization is required to ensure that shutdownRequested the visibility of the variable is implemented correctly. (may be called from a JMX listener, an action listener in the GUI event thread, through RMI, through a Web service, and so on). However, synchronized writing loops using blocks is much more cumbersome than using the volatile status flags shown in Listing 2. Because volatile simplifies encoding and the status flag does not depend on any other state within the program, it is well suited to use volatile.

A common feature of this type of status token is that there is usually only one state transition; The shutdownRequested flag false is converted to true , and then the program stops. This pattern can be extended to a state flag that transitions back and forth, but can only be extended (from false to, to, and true converted) If the conversion period is not detected false . In addition, some atomic state transition mechanisms, such as atomic variables, are also required.

Mode #2: One-time security release (one-time safe publication)

Lack of synchronization results in the 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 may encounter an updated value for an object reference (written by another thread) and the old value of the state of the object exists at the same time. (This is the source of the famous double-check lock (double-checked-locking) problem, where the object reference is read without synchronization, and the problem is that you might see an updated reference, but still see the incompletely constructed object through the reference).

One technique for implementing a secure publishing object is to define an object reference as a volatile type. Listing 3 shows an example where a background thread loads some data from the database during the startup phase. Other code will check whether the data has been published before it is used when it can take advantage of this data.

Listing 3. Use volatile variables for a one-time security release
public class Backgroundfloobleloader {public    volatile flooble theflooble;    public void Initinbackground () {        //do lots of stuff        theflooble = new flooble ();  The only write to theflooble    }}public class SomeOtherClass {public    void DoWork () {while        (true) { c8/>//do some stuff            ... Use the flooble, but only if it's ready            if (floobleloader.theflooble! = null)                 dosomething (Floobleloader.thef looble);}}}    

If the theFlooble reference is not a volatile type, the code in the dereference will doWork() theFlooble get an incomplete construct Flooble .

A prerequisite for this pattern is that the published object must be thread-safe or a valid immutable object (valid immutable means that the state of the object will never be modified after it is published). A reference to a volatile type ensures the visibility of the object's published form, but if the state of the object changes after it is published, additional synchronization is required.

Mode #3: Independent observation (independent observation)

Another simple pattern for safe use of volatile is to periodically "publish" the results for internal use by the program. For example, suppose there is an ambient sensor that can feel the ambient temperature. A background thread might read the sensor every few seconds and update the volatile variable that contains the current document. The other thread can then read the variable, allowing it to see the latest temperature values at any time.

Another application that uses this pattern is to collect statistics about the program. Listing 4 shows how the authentication mechanism remembers the name of the user who last logged on. Use the recurring lastUser citation to publish the value for use by other parts of the program.

Listing 4. Use volatile variables for publication of multiple independent observation results
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 pattern is an extension of the preceding pattern, and a value is published to be used elsewhere within the program, but unlike the release of a one-time event, this is a series of independent events. This mode requires that the value being published is valid immutable-that is, the status of the value will not change after it is published. The code that uses this value needs to be clear that the value may change at any time.

Mode #4: "Volatile bean" mode

The volatile bean pattern applies to the framework that uses JavaBeans as the "honor structure." In volatile bean mode, JavaBean is used as a container for a set of independent properties that have getter and/or setter methods. The basic principle of the volatile bean pattern is that many frameworks provide containers for variable data holders (for example HttpSession ), but the objects placed in those containers must be thread-safe.

In volatile bean mode, all data members of the JavaBean are volatile types, and getter and setter methods must be very common-no logic can be included except to get or set the corresponding property. In addition, for data members referenced by an object, the referenced object must be valid and immutable. (This disables the property with the array value, because when the array reference is declared volatile , only the reference, not the array itself, has the volatile semantics). For any volatile variable, the invariant or constraint cannot contain the JavaBean property. The example in Listing 5 shows a JavaBean that adheres to the volatile bean pattern:

Listing 5. The person object that adheres to the volatile bean pattern
@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.) {         this.age = age;    }}
Advanced Mode of Volatile

The patterns described in the previous sections cover most of the basic use cases, and using volatile in these patterns is very useful and simple. This section describes a more advanced pattern in which volatile will provide performance or scalability benefits.

The advanced mode of volatile applications is very fragile. Therefore, the hypothetical conditions must be carefully proven, and these patterns are tightly encapsulated, because even very small changes can damage your code! Similarly, the reason for using more advanced volatile use cases is that it improves performance and ensures that this performance benefit is really determined before you begin applying advanced mode. These patterns need to be weighed to give up readability or maintainability in exchange for possible performance gains--if you don't need to improve performance (or you can't prove you need it with a rigorous test program), then this is probably a bad deal because you're likely to lose the candle, It is worth less than what you give up.

Mode #5: Low overhead read-write lock policy

So far, you should understand that volatile functionality is not enough to implement counters. Because ++x it is actually a simple combination of three operations (read, add, store), if more than one thread happens to attempt to perform an incremental operation on the volatile counter at the same time, its updated value may be lost.

However, if the read operation is far more than the write operation, you can use the internal lock and the volatile variable to reduce the cost of the common code path. The thread-safe counters shown in Listing 6 use to synchronized ensure that incremental operations are atomic and use volatile the visibility that guarantees the current results. This approach achieves better performance if the update is infrequent, because the cost of the read path involves only volatile read operations, which is usually better than the overhead of a non-competitive lock acquisition.

Listing 6. Using volatile and synchronized to achieve "less expensive read-write locks"
@ThreadSafepublic class Cheesycounter {    //employs the cheap read-write lock trick    //all mutative operations must Be do with the ' this ' lock held    volatile int value;    public int GetValue () {return value;}    synchronized int increment () {        return value++;    }}

This technique is called a "low-cost read-write lock" because you use a different synchronization mechanism for read and write operations. Because the write operation in this example violates the first condition that uses volatile, you cannot use volatile to safely implement counters--you must use locks. However, you can use volatile in a read operation to ensure the visibility of the current value, so you can use the lock to make all the changes, using volatile for read-only operations. Where the lock allows only one thread to access the value at a time, volatile allows multiple threads to perform read operations, so when using the volatile guarantee to read the code path, it is much more shared-as in read-write operations-than using locks to execute all code paths. However, it is important to keep in mind the weaknesses of this model: if you go beyond the most basic application of the pattern, it will become very difficult to combine these two competing mechanisms.

Conclusion

Volatile variables are a very simple, yet very fragile synchronization mechanism compared to locks, which in some cases provides better performance and scalability than locks. If you strictly follow the use of volatile conditions-that is, the variable is really independent of the other variables and its own previous values-in some cases you can use the volatile substitution synchronized to simplify the code. However, volatile the code used is often more error-prone than code that uses locks. The pattern described in this article covers some of the most common use cases that can be used volatile instead synchronized . Following these patterns (not exceeding their limits when using them) can help you implement most use cases safely and use volatile variables for better performance.

Reference Learning
    • You can refer to the original English text on the DeveloperWorks global site in this article.
    • Java Concurrency in practice: How-to Manual for developing concurrent programs using Java code, including building and writing thread-safe classes and programs, avoiding performance impacts, managing performance, and testing concurrent applications.
    • Popular Atom: Introduces the new Atom variable class in Java 5.0, which extends the volatile variable to support atomic state transitions.
    • Introduction to non-blocking algorithms: Describes how to implement concurrency algorithms using atomic variables instead of locks.
    • Volatiles: Get more information about volatile variables from Wikipedia.
    • Java Technology Zone: Hundreds of articles on various aspects of Java programming are available.

Java Theory and Practice: using Volatile variables correctly

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.