contexts
The volatile keyword is one of the more important keywords in concurrent programming. It guarantees the visibility of variables/objects in memory and prohibits instruction reordering, avoiding visibility problems caused by CPU or compiler optimizations.
In concurrent programming, volatile can be used to modify a variable or an object (e.g., the singleton pattern uses volatile to modify a singleton object).
give an example
volatile int a = 100;
volatile SingleInstance instance;*
What is visibility? What is the visibility problem?
Answer: Visibility means that when a shared variable is modified by a thread, other threads can immediately see the changed value of the variable. Because threads perform logical operations on data in their own working memory and do not manipulate the value of a variable in main memory, once a thread updates the value of a variable, it must immediately update it again to main memory if it wants to be visible. So, the visibility problem is that thread A updates the value of the variable, and the other thread B manipulates the variable without getting the new value of the changed variable, i.e., the value changed by thread A is not visible, and thread B performs an update operation on the variable using the old value, thus making the data inconsistent. This is the visibility problem!
How to solve the variable visibility problem?
A: There are many solutions to the visibility problem in java. Such as synchronzied, volatile, Lock lock, Atomic class under Atomic package, class under JUC.
The main purpose of synchronzied is to ensure that only one thread can operate on a shared variable at the same time. This avoids visibility problems caused by multiple threads accessing a shared variable at the same time.
The main thing about volatile is that it will make sure that every thread reads the latest value of that variable from main memory and not from its own cache. This post focuses on the underlying principles of volatile.
The principle of volatile implementation
volatile is a memory barrier that prohibits instruction reordering and thus guarantees visibility.
Memory barriers are write barriers and read barriers (these barriers are actually hardware or compiler level instructions), the store instruction is called immediately after a volatile variable is updated to ensure that all previous writes are refreshed to main memory and to avoid reordering of writes. The read barrier ensures that the latest value is read from main memory before the volatile variable is read. Most processors use the StoreLoad barrier.
StoreLoad is the equivalent of a full barrier that stores the processor's instruction to assign a variable to the Store Buffer, and then the lock instruction flushes the data in the Store Buffer to the cache line, invalidating the cache line where the variable is cached by the other CPUs.
So the underlying memory barrier actually calls the Lock instruction.
happens before model
Some of the specifications in this JMM essentially describe the sequential relationship between two operation instructions. If there is a hapens-before relationship between operation A and operation B, then it means that the result of the execution of operation A is visible to operation B.
Here are some Happens-before rules
Happens-before rule
- Principle of procedural order
In the same thread, if operation x precedes operation y, then x happens before y, which is actually as-if-serial semantics. - Transmissibility rules
If there exists A happens before B; B happens before C, then there must exist A happens before C . - Rules for volatile variables
This refers to the use of memory barriers to ensure that writes to a volatile modified variable always happen before reads. - Monitor Lock Rules
A thread's release of a lock must happen before a subsequent thread's addition of the lock.
public void monitor() {
synchronzied(this) {
if(x == 0) {
x = 10;
}
}
As shown in the code, when thread A executes the logic before adding the lock, and at the end of the execution, the lock will be released, at which time the result produced by A must be visible to B.
- The start rule
If a thread A calls the start method of a child thread, then all operations in thread A before the call to the start() method hapens-before all operations in thread B. - join rule
The first thing join() does is wait for the result of the execution of some subthread.
If the main thread main() executes the join() method of thread A and returns successfully, then any operation in thread A hapens before the return of the join() method of the main thread.