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.
Volatile variables in the Java language can be thought of as a "light synchronized", and volatile variables require less coding and run-time overhead than synchronized blocks. But the functionality it can achieve is only part of the synchronized. This article introduces several modes of efficient use of volatile variable downloads, and highlights several scenarios where volatile variables are not suitable for use.
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 visibility characteristics, but they do not have atomic properties. This means that threads can automatically discover the latest values of volatile variables. Download 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 the value of X remain constant during 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 common as synchronized for 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. Download
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 defining the lower and upper fields as volatile types does not fully implement the thread safety of the class, so synchronization is still required. Otherwise, if it happens that two threads execute Setlower and setupper at the same time using inconsistent values, the scope will be in an inconsistent state. For example, if the initial state is (0, 5), and thread A calls Setlower (4) at the same time, and thread B calls Setupper (3), it is clear that the values crossed by these two operations are not eligible, then two threads pass the check to protect the invariant, so that the final range value is (4 , 3)--An invalid value. As for the other operations on the scope, we need to atomically setlower () and Setupper () operations--and it is not possible to define a field as a volatile type.
Performance Considerations Download
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 the cost of volatile and synchronized 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 mode download
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 () {
while (!shutdownrequested) {
Do stuff
}
}
It is possible to call the shutdown () method from outside the loop-that is, in another thread-so you need to perform some kind of synchronization to ensure that the visibility of the shutdownrequested 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, writing loops using synchronized 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. Download
A common feature of this type of state token is that there is usually only one state transition; the shutdownrequested flag is converted from false to true, and then the program stops. This pattern can be extended to a state flag that is converted back and forth, but can be extended only if the conversion period is not detected (from false to true and then to 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 can use this data, before using it will check whether the data has been published, and here, many people are going to learn to participate in the Java training course, the Java education package is the job credible .
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) {
Do some stuff ...
Use the flooble, but only if it's ready
if (floobleloader.theflooble! = null)
DoSomething (floobleloader.theflooble);
}
}
}
If the theflooble reference is not a volatile type, the code in DoWork () will get a flooble that is not fully constructed when the reference to Theflooble is lifted.
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. Download
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. Lastuser is used repeatedly to publish values 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 properties with array values, because when an array reference is declared as 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
@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.) {
This.age = age;
}
}
Volatile Advanced Mode Download
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-cost read-write lock policy download
So far, you should understand that volatile functionality is not enough to implement counters. Because ++x is actually a simple combination of three operations (read, add, store), it is possible that multiple threads might lose their updated values if they happen to attempt to perform incremental operations on the volatile counter at the same time.
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 synchronized to ensure that the incremental operation is atomic and to use volatile to guarantee the visibility of the current result. 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"
@ThreadSafe
public class Cheesycounter {
Employs the cheap read-write lock trick
All mutative operations must is done with the ' this ' lock held
@GuardedBy ("This") private volatile int value;
public int GetValue () {return value;}
public 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 truly independent of the other variables and its own previous values-in some cases you can use volatile instead of synchronized to simplify the code. However, code that uses volatile is often more error-prone than code that uses locks. The pattern described in this article covers some of the most common use cases where volatile can be used instead of 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.
Attached: Two sections of code download with thread safety issues
Java code
public class Test40 {
public static void Main (string[] args) throws Interruptedexception {
for (int i = 0; i < i++) {
SYSTEM.OUT.PRINTLN (Test ());
}
}
private static int test () throws Interruptedexception {
concurrenthashmap<string, integer> map = new concurrenthashmap<string, integer> ();
Executorservice pool = Executors.newcachedthreadpool ();
for (int i = 0; i < 8; i++) {
Pool.execute (new MyTask (map));
}
Pool.shutdown ();
Pool.awaittermination (1, timeunit.days);
Return Map.get (Mytask.key);
}
}
Class MyTask implements Runnable {
public static final String key = "KEY";
Private concurrenthashmap<string, integer> map;
Public MyTask (concurrenthashmap<string, integer> map) {
This.map = map;
}
@Override
public void Run () {
for (int i = 0; i <; i++) {
This.addup ();
}
}
private void Addup () {
if (!map.containskey (KEY)) {
Map.put (KEY, 1);
} else {
Map.put (Key, Map.get (key) + 1);
}
}
}
Java code
public class Test40 {
public static void Main (string[] args) throws Interruptedexception {
for (int i = 0; i < i++) {
SYSTEM.OUT.PRINTLN (Test ());
}
}
private static int test () throws Interruptedexception {
concurrenthashmap<string, integer> map = new concurrenthashmap<string, integer> ();
Executorservice pool = Executors.newcachedthreadpool ();
for (int i = 0; i < 8; i++) {
Pool.execute (new MyTask (map));
}
Pool.shutdown ();
Pool.awaittermination (1, timeunit.days);
Return Map.get (Mytask.key);
}
}
Class MyTask implements Runnable {
public static final String key = "KEY";
Private concurrenthashmap<string, integer> map;
Public MyTask (concurrenthashmap<string, integer> map) {
This.map = map;
}
@Override
public void Run () {
for (int i = 0; i <; i++) {
This.addup ();
}
}
Private synchronized void Addup () {//modifier addup method with keyword synchronized
if (!map.containskey (KEY)) {
Map.put (KEY, 1);
} else {
Map.put (Key, Map.get (key) + 1);
}
}
}
Volatile in Java