Detailed explanation of Volatile keyword in Java

1. Basic Concepts


Let's add concepts first: visibility, atomicity, and ordering in the Java memory model.

Visibility:

  Visibility is a complex property because mistakes in visibility always go against our intuition. In general, we cannot ensure that the thread performing the read operation will see the value written by the other thread in a timely manner, and sometimes it is not even possible at all. To ensure the visibility of memory writes between multiple threads, a synchronization mechanism must be used.

  Visibility refers to the visibility between threads, the state modified by one thread is visible to another thread. That is, the result of a thread modification. Another thread to see right away. For example: variables modified with volatile will have visibility. Variables modified by volatile do not allow internal thread caching and reordering, that is, direct memory modification. So it is visible to other threads. But there is a problem to pay attention to here, volatile can only make the content modified by him visible, but it cannot guarantee its atomicity. For example, volatile int a = 0; there is an operation a++ after that; the variable a has visibility, but a++ is still a non-atomic operation, that is, this operation also has thread safety issues.

  In Java, volatile, synchronized, and final implement visibility.

Atomicity:

  Atom is the smallest unit in the world and is indivisible. For example, a=0; (a non-long and double types) This operation is inseparable, then we say that this operation is an atomic operation. Another example: a++; This operation is actually a = a + 1; is divisible, so it is not an atomic operation. Non-atomic operations will have thread safety problems, and we need to use synchronization technology (synchronized) to make it an atomic operation. An operation is atomic, then we call it atomic. Some atomic classes are provided under the concurrent package of java. We can understand the usage of these atomic classes by reading the API. For example: AtomicInteger, AtomicLong, AtomicReference, etc.

  Synchronized in Java and operations in lock and unlock guarantee atomicity.

Orderly:

  The Java language provides two keywords, volatile and synchronized, to ensure the ordering of operations between threads. Volatile is because it contains the semantics of "forbidding instruction reordering", and synchronized is defined by "a variable only allows one operation at the same time." The thread lock operation is obtained by this rule, which determines that two synchronized blocks holding the same object lock can only be executed serially.

The following is excerpted from Java Concurrency in Practice:

  The following piece of code will have problems in a multi-threaded environment.

640?wx_fmt=png&wxfrom=5&wx_lazy=1

NoVisibility may continue to loop because the read thread may never see the ready value. Even NoVisibility may output 0, because the reading thread may see the value written to ready, but not the value written to number later, a phenomenon called "reordering". As long as a reordering cannot be detected in a thread (even if the reordering in that thread is visibly visible in other threads), then there is no guarantee that operations in a thread will be performed in the order specified in the program. When the main thread first writes to number, and then writes to ready without synchronization, then the order that the reading thread sees may be the exact opposite of the order in which it was written.

  Without synchronization, the compiler, processor, runtime, etc. may all make some unexpected adjustments to the order in which operations are performed. In a multithreaded program that lacks sufficient synchronization, it is impossible to get the correct conclusion in order to judge the execution of memory operations.

  This may seem like a failed design, but it enables the JVM to take full advantage of the power of modern multi-core processors. For example, in the absence of synchronization, the Java memory model allows the compiler to reorder operations and cache values ​​in registers. Additionally, it allows the CPU to reorder operations and cache values ​​in processor-specific caches.


2. Volatile principle


  Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

  在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

640?wx_fmt=png

  当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。

  而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。

当一个变量定义为 volatile 之后,将具备两种特性:

  1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。

  2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

volatile 性能:

  volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324526075&siteId=291194637