As is known to all, unlimited under multi-threaded operation sharing variables is dangerous, in order to ensure thread safety semantics, the general recommendation is to operate the shared variable lock, for example, in the method of synchronized keyword modification to read and write shared variables.
But synchronized expensive, is there a lighter and more elegant solution?
Volatile is a lightweight synchronized that, if properly used, can achieve the same thread-safe semantics as synchronized without the overhead of thread switching.
What is the role of volatile?
Volatile guarantees the "visibility" of shared variables. Visibility means that when a thread modifies a shared variable, another thread can read the modified value. It is less expensive in some cases than synchronized.
This sentence may be difficult to understand, let me give an example.
Before I wrote this article <ticket lock, CLH lock, MCS lock>, the first given example of naive Lock, the flag variable is declared volatile.
What happens if flag is not volatile but a normal variable?
Imagine a scene where thread A is occupying a lock and thread B spins the flag variable. Now thread A exits the critical section, and the flag is set to false. But can thread B immediately observe the change in flag?
Unfortunately, not necessarily.
Because there are multiple cores in the modern CPU, each core has its own cache (cache), thread A's modification of flag is written only on the cache, and it takes an indeterminate amount of time to be flushed into main memory. Thread B's core also caches the flag variable in the cache so that thread B is not necessarily visible even if the flag variable in main memory changes.
Therefore, if flag is a normal variable, naive lock is not tenable.
If flag is set to a volatile type, the JVM guarantees that any write to the flag variable will be immediately flushed into main memory, and that the corresponding cache line of the core that caches the flag variable will be invalidated, forcing other threads to read the latest value from main memory.
This enables shared variables to be modified by one thread and the semantics that other threads can read immediately.
So what is the underlying principle of the volatile keyword? How does it allow write operations to be flushed directly to main memory, and how does the other core cache line fail?
If you look at the compiled machine code, you will see that after the write operation of the volatile variable, an instruction is appended
Lock Addl $0x0, (%ESP);
It is easy to see, Addl $0x0, (%ESP) This sentence itself is not any effect, the effect and NOP such as a idling instruction equivalent, but the front of the lock prefix, a bit of meaning.
Check out Intel's <intel? IA-32 architectures software Developer ' s manual>
8.1.4 Effects of a LOCK operation on Internal Processor Caches
For the Intel486 and Pentium proc Essors, the lock# signal is all asserted on the bus during a-LOCK operation, even if the area of memory being locked is Cached in the processor.
For the P6 and more recent processor families, if the area of memory being locked during a LOCK operation is Cach Ed in the processor so is performing the LOCK operation as write-back memory and are completely contained in a cache line , the processor may not assert the lock# signal on the bus. Instead, it'll modify the memory location internally and allow it's cache coherency mechanism to ensure that the Operati On are carried out atomically. This operation is called "Cache locking." The cache coherency mechanism automatically prevents or more processors that has cached the same area of memory from Simultaneously modifying data in this area.
Presumably, the cache line on the corresponding core will be forced back into main memory when the lock command is encountered. Then because of the effect of the cache consistency protocol (see my Blog < cache conformance protocol >), the corresponding cache line on other cores is also set to invalid.
The semantics of the volatile are then fulfilled, requiring only this lock command.
But does volatile guarantee atomicity? For example, if we multithreading to a volatile variable to do the self-increment operation, is this thread-safe?
Answer: not really.
We imagine that there are two threads that do self-increment for variable count of volatile type, count initial value is 0, two threads get 0 at the same time, increment to 1, and then write back at the same time, so the result of Count is 1, which does not meet the expectation.
So what should we do?
Refer to Atominteger, using the CAS operation provided by unsafe to update count, in the above scenario, two threads simultaneously execute CAS (count, 0, 1), only one of the threads can execute successfully, another fails to retry, and the new count value is read, Then execute the CAS (count, 1, 2), this time execution succeeds, then the final value of Count is 2. Meet expectations.
Resources:
Talk about concurrency (i) in-depth analysis of the implementation principle of volatile
Deep understanding of the volatile keyword
Intel? IA-32 architectures software Developer ' s manual,8.1.4 Festival, p257
To parse the Java volatile keyword