The volatile variable provides thread visibility and does not guarantee thread security and atomicity.
What is thread visibility:
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.
For details, refer to the volatile Syntax:
Volatile is equivalent to the weak implementation of synchronized. That is to say, volatile implements synchronized-like semantics, but there is no lock mechanism. It ensures that updates to the volatile field inform other threads in a foreseeable way.
Volatile contains the following semantics:
(1) The Java storage model does not reorder the operations of the valatile command: this ensures that the operations on the volatile variable are executed in the order in which the commands appear.
(2) The volatile variable is not cached in the register (only visible to the owner thread) or is invisible to the CPU. The volatile variable is always read from the main memory each time. That is to say, changes to volatile variables are always visible to other threads, and they are not used for internal variables in their own thread stacks. That is to say, in the happens-before law, after a write operation on a valatile variable, any subsequent read operations can understand the result of this write operation.
Although the volatile variable has good features, volatile does not guarantee thread security. That is to say, the volatile field operation is not atomic, volatile variables can only ensure visibility (after a thread is modified, other threads can understand the results after this change). To ensure atomicity, the variable can only be locked so far!
Volatile principles:
The following three principles apply to volatile variables:
(1) writing a variable does not depend on the value of this variable, or only one thread can modify this variable.
(2) The state of the variable does not need to be involved in the constant constraint with other variables.
(3) No locks are required for access 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 three 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.
Correct use of volatile:
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:
Mode #4: "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.
Command Re-sorting and Happens-before rules
Command Re-sorting
The Java language specification specifies that the JVM thread maintains the ordered semantics, that is, as long as the final result of the program is equivalent to the result in a strictly ordered environment, the command execution sequence may be different from the code sequence. This process is called Command Re-sorting. The significance of Command Re-sorting is that the JVM can properly re-Sort machine commands based on the characteristics of the processor (CPU multi-level cache system, multi-core processor, etc, make the machine commands more in line with the characteristics of CPU execution and maximize the performance of the machine.
The simplest model of program execution is to execute commands in the order they appear. This is irrelevant to the CPU used to execute commands and maximizes the portability of commands. The terminology of this model is called the ordered consistency model. However, neither the modern computer system nor the processor architecture guarantees this (because manual designation does not always ensure that it meets the CPU processing characteristics ).
Happens-before law
The Java storage model has A happens-before principle, that is, if action B needs to see the execution result of action A (whether A/B is executed in the same thread or not ), then A/B needs to satisfy the happens-before relationship.
Before introducing the happens-before rule, we will introduce a concept: JMM Action (Java Memeory Model Action), Java stores Model actions. An Action includes read/write of variables, lock and release of monitors, start () and join () of threads (). The lock will be mentioned later.
Happens-before complete rules:
(1) Every Action in the same thread is happens-before in any Action that appears after it.
(2) unlocking a monitor happens-before locks each subsequent to the same monitor.
(3) write operations on the volatile field happens-before are performed on each subsequent read operation of the same field.
(4) the call of Thread. start () will call the action of happens-before in the startup Thread.
(5) All actions in Thread are returned by happens-before in other threads to check whether the Thread ends or Thread. join () or Thread. isAlive () = false.
(6) When thread A calls interrupt () of thread B of another thread B, both happens-before and thread A find that thread B is interrupted by thread A (B throws an exception or A detects isInterrupted () of thread B () or interrupted ()).
(7) End of an object constructor: The beginning of happens-before and finalizer of the object
(8) If action A is in action B and action B is in action C, Action A is in action C.