Introduction to volatile
Overview of volatile
- volatile is a
比synchronized
keyword更轻量级
,同步机制
and the operation不
will be performed when accessing the volatile variable加锁
, so it不
will be executed线程阻塞
. - volatile assurance
可见性
and禁止指令重排序
, through the bottom is "内存屏障
to achieve", but不保证原子性
. 写入
volatile variable is equivalent to退出同步代码块
,读取
volatile variable equivalent进入同步代码块
.
volatile usage scenarios
- The
写
input operation of the variable不依赖变量的当前值
may ensure只有单个线程更新
the value of the variable; - The variable
不会与其他状态变量一起纳入不变性条件中
; (The variable is not included in the invariant of other variables) - When accessing variables
不需要加锁
.
Use Cases
状态标记量
: According to the status flag, terminate the thread. That is, volatile can be used in scenarios where a certain status flag is checked to determine whether to exit the loop.单例模式中的double check
: Volatile modify instance becauseinstance = new Singleton()
this line of code is not an atomic operation, allocate memory to instance, call Singleton's constructor to initialize member variables, and point the instance object to the allocated memory space
JMM's special rules for the definition of volatile variables:
- The current thread
使用变量前都
must be先从主内存刷新最新的值
used every time to ensure that you can see the modification of variables by other threads. (read、load、use连续执行
, From主
memory to工作
memory.) - Currently
修改变量后
, you must go立刻同步
to the main memory every time to ensure that other threads can see their modifications to the thread. (assign、store、write
Continuous execution, from工作
memory to主
memory.) - Variables modified by volatile
不会被指令重排序优化
ensure that the execution order of the code is the same as the order of the program.
Visibility && Principle of prohibiting instruction reordering optimization
(Realized by the underlying lock instruction)
volatile can guarantee thread visibility and provide a certain order, but can not guarantee atomicity, volatile is implemented by " 内存屏障
" in the bottom layer of JVM . Two layers of semantics: 保证可见性,不保证原子性
; 禁止指令重排序
.
Visibility
Ensure the visibility of volatile variables to all threads . Visibility means that when a thread modifies the value of this variable, the new value is immediately known to other threads. Ordinary variables are not allowed. The transfer of values of common variables between threads needs to be done through main memory. For example, thread A modifies the value of a common variable and writes back to main memory. Another thread B writes after thread A finishes writing Only after reading from the main memory, the new variable value is visible to thread B.
The volatile bottom layer uses the lock prefix to cause the Cache of this CPU to be written into memory . This writing action will also cause other CPUs or other cores to invalidate its Cache , which is equivalent to JMM's " store and write " of variables in the Cache. "Operation, this thread invalidates the cache line of the variable of other threads . When other threads need to read the variable and find that the variable cache line is invalid , the data is reloaded from the main memory , so the visibility of the data is guaranteed .
Writing volatile variables is equivalent to exiting the synchronization code block , and reading volatile variables is equivalent to entering the synchronization code block . The locking mechanism can ensure both visibility and atomicity, but volatile variables only guarantee visibility, not atomicity .
Prohibit instruction reordering optimization
When the variable is declared as volatile, the compiler and runtime will notice that the variable is shared , because the operation on the variable will not be reordered with other memory operations, and the volatile variable will not be cached in the register or other Where the processor is not visible.
Variables modified by volatile will perform an additional lock operation in the assembly code , which is equivalent to a memory barrier (Memory Barrier, which means that the subsequent instructions cannot be reordered to the position before the memory barrier when reordering ).
There is no need for a memory barrier when only one CPU accesses memory; but if multiple CPUs access the same memory, and one of them is observing the other, a memory barrier is needed to ensure consistency. When the lock instruction synchronizes the modification to the memory, it means that all previous operations have been performed, which can form a feeling that the instruction reordering cannot cross the memory barrier .
The principle of volatile prohibiting instruction reordering:
- When
第二个操作是volatile写
the time, no matter what the first operation is都不能重排序
. This rule ensures that operations before volatile writes will not be reordered by the compiler after volatile writes. - When
第一个操作是volatile读
the time, no matter what the second action is都不能重排序
. This rule ensures that operations after volatile reads will not be reordered by the compiler before volatile reads. - When
第一个操作是volatile写
,第二个操作是volatile读
when,不能重排序
.
Q&A
What is the reason for as-if-serial semantics to allow reordering of operations that have control dependencies?
In a single-threaded program, reordering operations that have control dependencies will not change the execution result; but in a multithreaded program, reordering operations that have control dependencies may change the execution results of the program.
What is the principle of happen-before?
- Sequential execution of code rules : in the same thread, the previous operation happens-before subsequent operations;
- Add unlock rule : the
解锁
operation on the monitor happens-before its subsequent加锁
operation; - Volatile rule :
volatile
the写
operation of variable happens-before subsequent读
operation; - Thread startup rules : thread
start()
method happens-before all subsequent operations of the thread; - Thread terminates rules : Thread All operations happen-before another thread calls on that thread
join()
returns after the success of the operation; - Thread interruption rules :
interrupt()
the call to the thread method happens-before the interrupted code detects the occurrence of an interruption event, and theThread.interrupted()
method can detect whether an interruption occurs. - Object termination rules : the beginning of a
对象的初始化完成
happen-beforefinalize()
method (end of constructor execution) . - Transitivity : If a happen-before b, b happen-before c, then a happen-before c.
What is the difference between locking and volatile?
Similarities :
writing volatile variables is equivalent to exiting the synchronization code block , and reading volatile variables is equivalent to entering the synchronization code block .
Differences: The
locking mechanism can ensure both visibility and atomicity ; but volatile variables only guarantee visibility , not atomicity .
What is the difference between ordinary variables and volatile variables?
volatile
The special rules 保证新值能立即同步
to 主内存
, each 使用前
capable of immediately 从主内存刷新
. So volatile can guarantee the 多线程
variable 可见性
while operating 普通变量不保证
.
- A volatile modified variable can guarantee "visibility" : when a thread modifies the value of this variable, the new value is immediately known to other threads.
- Ordinary variables cannot guarantee "visibility" : the transfer of values of common variables between threads requires main memory to complete. Thread A modifies the value of a common variable, first locks to read from main memory, and then writes back to main memory (Lock, read, load, use, assign, store, write, unlock). Another thread B will read the latest value (lock, read, load, use, unlock) from the main memory after the write back of thread A is completed (unlocked), and the new variable value will be visible to thread B.
What is the semantic meaning of atomicity?
volatile不保证原子性
, But talk about atomic semantics.
1) is defined atom:
i.e. a plurality of operations or operations 要么全部执行
and performed 过程不会
by any factor 打断
, 要么都不执行
.
2) Example:
i = 2;
i = j ;
i++;
i = j + 2;
In Java, to ensure that only the basic data types of variables and assignment operations are atomic operations (if it is in the 32-bit JDK environment for long和double
the 64位数据
of the 读取不是原子
operation, will be divided into high and low 两次32位操作
.) May be a single thread, as atomic entire procedure Sexuality, but multithreading needs to ensure atomicity through synchronized and locks.
i = 2
The operation is 原子的
only involved in assigning basic data type 2 to i;
i = j, yes 两个操作
, take the value of j first, and then assign the value of j to i , 非原子操作
;
i ++, yes 三个操作
, take the value of i first, and then increment the value of i, Finally, assign the incremented result to i, yes 非原子操作
;
i = j + 2, yes 三个操作
, take the value of j first, then j + 2, and finally assign the result of j + 2 to i, yes 非原子操作
;
Before JDK1.5, why Java cannot safely use DCL (double lock detection) to implement singleton mode?
The semantics of the reordering of volatile masking instructions were only completely fixed in JDK1.5. The code before and after the volatile variable still has reordering issues.
1) DCL singleton mode :
public class Singleton {
// volatile修饰变量
private volatile static Singleton instance;
private Singleton(){}
// 单例方法
public static Singleton getInstance(){
if(instance == null){
// 加锁synchronized代码块
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton.getInstance();
}
}
2) Use static inner classes to implement a safer mechanism
/* 静态内部类 */
public class SingletonInner {
// 静态内部类
private static class Holder {
private static SingletonInner singleton = new SingletonInner();
}
private SingletonInner(){}
public static SingletonInner getSingleton(){
return Holder.singleton;
}
}