作者:tomas家的小拨浪鼓
链接:https://www.jianshu.com/p/55a66113bc54
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
volatile
volatile
的特性volatile
变量自身具有下列特性:
volatile
变量的读,总是能看到(任意线程)对这个 volatile
变量最后的写入。 volatile
变量的读/写具有原子性,但类似于 volatile
++这种复合操作不具有原子性。Q:volatile
是如何保证可见性的了
A: 在多核处理器中,当进行一个volatile
变量的写操作时,JIT编译器生成的汇编指令会在写操作的指令前在上一个“lock”前缀。“lock”前缀的指令在多核处理器下会引发了两件事情:
volatile
变量自身的操作,都会使其所在缓存行的数据会写回到主存中,这就使得其他任意线程对该缓存行中变量的读操作总是能看到最新写入的值( 会从主存中重新载入该缓存行到线程的本地缓存中 )。当然,也正是因为缓存每次更新的最小单位为一个缓存行,这导致在某些情况下程序可能出现“伪共享”的问题。嗯,好像有些个跑题,“伪共享”并不属于本文范畴,这里就不进行展开讨论。好了,目前为止我们已经了解volatile
变量自身所具有的特性了。注意,这里只是volatile
自身所具有的特性,而volatile
对线程的内存可见性的影响比volatile
自身的特性更为重要。
volatile
写-读建立的 happens before
关系happens-before 规则中有这么一条:
volatile
变量规则:对一个volatile
域的写,happens-before于任意后续对这个volatile
域的读。
happens-before的这个规则会保证volatile
写-读具有如下的内存语义:
volatile
写的内存语义: volatile
变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。volatile
读的内存语义: volatile
变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。 volatile
的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。因为内存屏障是一组处理器指令,它并不由JVM直接暴露,因此JVM会根据不同的操作系统插入不同的指令以达成我们所要内存屏障效果。 volatile
写的后面插入一个 StoreLoad
屏障。
StoreLoad
屏障 指令示例:
Store1
;StoreLoad
;Load2
确保Store1
数据对其他处理器变得可见(指刷新到内存)先于Load2
及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他3个屏障的效果(LoadLoad Barriers、StoreStore Barriers、LoadStore Barriers)按转载者的理解,作者想表达的是
Store1
是存储数据到内存的操作;StoreLoad
是’StoreLoad
‘屏障;Load2
是读取内存的数据的操作.然后StoreLoad
是为了确保Store1
存储数据到内存的操作,能够先于Load2及所有屏障后面装载指令的操作。StoreLoad
屏障会使该屏障之前的所有内存访问指令(存储和装载指令)完成后才能执行该屏障之后的内存访问指令。StoreLoad
屏障是一种“全能型”的屏障,它同时具有其他3个屏障的效果[LoadLoad
屏障、StoreStore
屏障、LoadStore
屏障]
好了,到现在我们知道了volatile
的内存语义( happens-before
关系 )会保证volatile
写操作之前的读写操作不会被重排序到volatile
写操作之后,并且保证了写操作后将线程本地内存(可能包含了多个缓存行)中所有的共享变量值都刷新到主内存中。保证了其他线程总是在volatile
写入后,能够读取中到该写入线程所共享出来的变量值。这是volatile
的happens-before
关系( 通过内存屏障实现 )给我们带来的结果。注意,这个是volatile
变量自身所描述的特性是不同的,因为volatile
他只是保证了volatile
变量的可见性,而volatile
的内存语义的happens-before
关系还能让线程能够在刷新(写)操作后不会因为环境的资源重排序功能,而带来不能读取到最新最正确的值。并带来变量可见性。
转载这篇文章的作者觉得
happens-before
的特性比volatile
的可见性更重要.
了,讨论到这里,我们重新来理解下weakCompareAndSet
的实现语义。也就是说,weakCompareAndSet
操作仅保留了volatile
自身变量的特性,而出去了happens-before规则带来的内存语义。也就是说,weakCompareAndSet
无法保证处理操作目标的volatile
变量外的其他变量的执行顺序( 编译器和处理器为了优化程序性能而对指令序列进行重新排序 ),同时也无法保证这些变量的可见性。
目前为止,我们已经能够明白compareAndSet
方法和weakCompareAndSet
方法的不同之处了。那么,接下来我们来看看这两个方法的具体实现:
public boolean compareAndSet(T obj, int expect, int update) {
if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
return unsafe.compareAndSwapInt(obj, offset, expect, update);
}
public boolean weakCompareAndSet(T obj, int expect, int update) {
if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
return unsafe.compareAndSwapInt(obj, offset, expect, update);
}
是的,你没有看错。这两个方法的实现完全一样。unsafe.compareAndSwapInt(obj, offset, expect, update);
中就是调用native方法了:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
由此可见,在JDK8乃至之前的版本,weakCompareAndSet
方法并没有被真是意义上的实现,目前该方法所呈现出来的效果与compareAndSet
方法是一样的。
基于JDK 9
在JDK 9中 compareAndSet
和 weakCompareAndSet
方法的实现有些许的不同
/**
* Atomically updates Java variable to {@code x} if it is currently
* holding {@code expected}.
*
* This operation has memory semantics of a {@code ` volatile`} read
* and write. Corresponds to C11 atomic_compare_exchange_strong.
*
* @return {@code true} if successful
*/
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
底层调用的native方法的实现中,cmpxchgb
指令前都会有“lock
”前缀了(在JDK 8中,程序会根据当前处理器的类型来决定是否为cmpxchg
指令添加lock
前缀。只有在CPU是多处理器(multi processors)的时候,会添加一个lock
前缀)。
同时多了一个@HotSpotIntrinsicCandidate
注解,该注解是特定于Java虚拟机的注解。通过该注解表示的方法可能( 但不保证 )通过HotSpot VM自己来写汇编或IR编译器来实现该方法以提供性能。
它表示注释的方法可能(但不能保证)由HotSpot虚拟机内在化。如果HotSpot VM用手写汇编和/或手写编译器IR(编译器本身)替换注释的方法以提高性能,则方法是内在的。
也就是说虽然外面看到的在JDK9中weakCompareAndSet
和compareAndSet
底层依旧是调用了一样的代码,但是不排除HotSpot VM会手动来实现weakCompareAndSet
真正含义的功能的可能性。