On the volatile implementation principle

synchronized is a heavyweight lock, the volatile is lightweight synchronized, it ensures the "visibility" of shared variables in multi-threaded development. If a variable using volatile, then it is much lower cost than use of synchronized, because it does not cause the thread context switching and scheduling.

Java programming language allows threads to access shared variables, in order to ensure shared variables can be accurately and consistently updated thread should ensure exclusive lock to get through this variable alone. Popular speak a variable that is modified if volatile, and it ensures that all Java threads to see the value of this variable is the same. If a thread of volatile modification of shared variables to be updated, other threads can immediately see the update, which is called the thread visibility.

Memory model concepts

But it is still a bit difficult to understand volatile, which is related with the Java memory model, so before volatile understand we need to understand the concepts related to Java memory model.

Operating system semantics

When the computer running the program, each instruction is executed in the CPU during execution inevitably involve reading and writing data. We know that the data is stored program running in main memory, then there will be a problem, read and write data in main memory is not fast CPU to execute instructions, if any interaction is required to deal with the main memory will greatly affect the efficiency, so there is a CPU cache. CPU CPU cache for a unique, only with the thread running in the CPU.

With the CPU cache solves the problem of efficiency, but it will bring a new problem: data consistency.

In the program operation, the copying operation will require the data to a CPU cache, the CPU can not deal with the main memory during operation, but to read and write data directly from the cache, only if the end of the run It will flush data to main memory.

Take a simple example:

i = i + 1;
复制代码

When a thread running this code, it reads from the main memory the value of i (i = 1 at this time is assumed), and then copy the cache to the CPU, and the CPU executes the operation + 1 (where i = 2), then the data is written to i = 2 tells the cache, and finally flushed to main memory.

In fact, doing so in a single thread is no problem, there is a problem in multiple threads. as follows:

假如有两个线程 A、B 都执行这个操作( i++ ),
复制代码

I values ​​according to our normal logical thinking main memory should = 3.

But the fact is this it? analyse as below:

两个线程从主存中读取 i 的值( 假设此时 i = 1 ),到各自的高速缓存中,
然后线程 A 执行 +1 操作并将结果写入高速缓存中,最后写入主存中,此时主存 i = 2 。
线程B做同样的操作,主存中的 i 仍然 =2 。所以最终结果为 2 并不是 3 。
这种现象就是缓存一致性问题。
复制代码

There are two solutions to solve the cache coherence:

通过在总线加 LOCK# 锁的方式
通过缓存一致性协议
复制代码

The first scenario there is a problem, it is to adopt a exclusive way to achieve, namely a bus-LOCK # lock, then only one CPU can run other CPU had blocked more efficiency is low.

The second option, cache coherency protocol (MESI protocol), it ensures that a copy of the shared variables used in each cache is consistent. The core idea is as follows: When a CPU when writing data, if found operating variable is a shared variable, it will notify the other CPU cache line informed of the variable is invalid, so other CPU when reading the variables found invalid will reload the data from main memory.

Java Memory Model

The above describes how the operating system level to ensure data consistency, let's look at the Java memory model, a little look at what it provides assurance to us, and which provides a method and mechanism in Java to let us making more than to ensure the correctness of the execution of thread programming.

In concurrent programming we usually encounter three basic concepts: atomicity, visibility, orderly. We look volatile.

Atomicity

Atomicity: i.e. a plurality of operation or operation, either all executed and execution of any factor will not be interrupted, or can not perform.

Like inside the atomic database transaction, we look at a simple example to the following:

i = 0;  // <1>
j = i ;  // <2>
i++;  // <3>
i = j + 1; // <4>
复制代码

The above four operations, there are a few which are atomic, that not a few? If not quite understand, you may think are atomic operations, in fact, is the only one atomic operation, the rest were not.

  1. In Java, the basic data types of variables and assignment operations are atomic operations.
  2. Contains two operations: read i, i is the value assigned to j.
  3. It consists of three operations: reading the value of i, i + 1, i +1 is assigned to the result.
  4. Like <3>

Then the 64-bit JDK environment, read and write 64-bit data whether it is the atom?

实现对普通long与double的读写不要求是原子的(但如果实现为原子操作也OK)
实现对volatile long与volatile double的读写必须是原子的(没有选择余地)
复制代码

Further, volatile compound is not guaranteed atomic operation

Visibility

Visibility means that when multiple threads access the same variable, a thread changes the value of this variable, other threads can immediately see the modified values.

In the above it has been analyzed in a multithreaded environment, operating a thread to shared variables to other threads are not visible.

Java provides volatile to ensure visibility.

When a variable is declared volatile, it indicates the invalid thread-local memory.

When a thread modifies shared variables he will immediately be updated to the main memory;

When another thread reads the shared variable, it reads directly from the main memory.

synchronize and lock can be guaranteed visibility.

Orderliness

Ordering: sequential execution program that is executed in the order code.

In the Java memory model, in order to allow efficient compiler and processor instruction reordering, the reordering of course it does not affect single-threaded operating results, but the impact will be multi-threaded.

Java provides volatile to ensure a certain degree of orderliness. The most famous example is the singleton pattern inside the DCL (double checks the lock).

Analysis of volatile principle

volatile and can guarantee the visibility of the thread provides a certain orderliness, but can not guarantee atomicity. In the JVM bottom, volatile is a "memory barrier" to achieve.

Above those remarks, there are two semantics:

保证可见性、不保证原子性
禁止指令重排序
复制代码

The first layer can not introduce semantics, instruction reordering highlighted below.

Instruction reordering

In order to improve performance when executing a program, the compiler and processor will typically do reordering instructions:

编译器重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
处理器重排序。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
复制代码

Instruction reordering have little impact on single-threaded, he will not affect the operating results of the program, but will affect the validity of multi-threading. Since instruction reordering affect the validity of multi-threaded execution, then we need to prohibit reordering. The JVM is how to stop the reordering of it?

This leads to happen-before principle

  1. Program sequence rules: a thread, according to the order of the code book EDITORIAL operation, happens-before writing to the subsequent operations.
  2. Blocking rule: a unLock operation, happens-before in the face of lock operation with a lock.
  3. volatile variable rules: Write operations on a volatile variable, happens-before on the back read operations on this variable. Note that back.
  4. Passing rules: if A happens-before operating Procedure B, operated C B happens-before operation, can be obtained, the operation A happens-before operation C
  5. Thread start rule: start method Thread objects, each one action happens-before this thread.
  6. Thread break the rules: the call to interrupt thread method, it happens-before the interrupted thread code detection to interrupt event occurs.
  7. Thread the end of the rule: thread in all the operations, all happens-before termination detection thread, we can end by Thread.join () method, Thread.isAlive () return value means to detect the thread has terminated.
  8. Begin initialization of an object is completed, happens-before its finalize () method: Object end rule

We look at the third point focused on Volatile rule: write to the volatile variables, happen-before subsequent reads.

In order to achieve volatile memory semantics, a reordering will JMM, which rules are as follows:

当第二个操作是 volatile 写操作时,不管第一个操作是什么,都不能重排序。
这个规则,确保 volatile 写操作之前的操作,都不会被编译器重排序到 volatile 写操作之后。
复制代码

With a little understanding of the principles happen-before, let us answer the question how the JVM is prohibited reordering?

观察加入 volatile 关键字和没有加入 volatile 关键字时所生成的汇编代码发现,
加入volatile 关键字时,会多出一个 lock 前缀指令。
lock 前缀指令,其实就相当于一个内存屏障。
内存屏障是一组处理指令,用来实现对内存操作的顺序限制。
volatile 的底层就是通过内存屏障来实现的。
复制代码

The figure is a memory barrier required for the above rule:

to sum up

volatile looks simple, but to understand it is quite difficult, here is a basic understanding of them.

somewhat volatile synchronized with respect to more lightweight, it can in some cases replace synchronized, but can not completely replace synchronized. Only in some cases to be able to use volatile, use it to the following two conditions must be met:

对变量的写操作,不依赖当前值。
该变量没有包含在具有其他变量的不变式中。
复制代码

volatile frequently used in the following scene: state flag variable, Double Check write multiple threads read a thread.

Guess you like

Origin juejin.im/post/5d82e18de51d4562165535c7
Recommended