内存屏障(也称为内存栅栏,屏障指令等)
先行发生原则,happens-before,本质是一个理论,一套规范,内存屏障基于该规范进行实现
Unsafe.class,JDK 中的屏障接口
//....code segment...
public native void loadFence();
public native void storeFence();
public native void fullFence();
Unsafe.java
/**
* Ensures lack of reordering of loads before the fence
* with loads or stores after the fence.
* @since 1.8
*/
public native void loadFence();
/**
* Ensures lack of reordering of stores before the fence
* with loads or stores after the fence.
* @since 1.8
*/
public native void storeFence();
/**
* Ensures lack of reordering of loads or stores before the fence
* with loads or stores after the fence.
* @since 1.8
*/
public native void fullFence();
Unsafe.cpp
UNSAFE_ENTRY(void, Unsafe_LoadFence(JNIEnv *env, jobject unsafe))
UnsafeWrapper("Unsafe_LoadFence");
OrderAccess::acquire();
UNSAFE_END
UNSAFE_ENTRY(void, Unsafe_StoreFence(JNIEnv *env, jobject unsafe))
UnsafeWrapper("Unsafe_StoreFence");
OrderAccess::release();
UNSAFE_END
UNSAFE_ENTRY(void, Unsafe_FullFence(JNIEnv *env, jobject unsafe))
UnsafeWrapper("Unsafe_FullFence");
OrderAccess::fence();
UNSAFE_END
OrderAccess.hpp
//声明四类屏障类型,读读,写写,读写,写读
class OrderAccess : AllStatic {
public:
static void loadload();
static void storestore();
static void loadstore();
static void storeload();
//....more code.....
}
orderAccess_linux_ x86.inline.hpp
#include "runtime/atomic.inline.hpp"
#include "runtime/orderAccess.hpp"
#include "runtime/os.hpp"
#include "vm_version_x86.hpp"
// Implementation of class OrderAccess.
inline void OrderAccess::loadload() { acquire(); }
inline void OrderAccess::storestore() { release(); }
inline void OrderAccess::loadstore() { acquire(); }
inline void OrderAccess::storeload() { fence(); }
inline void OrderAccess::acquire() {
volatile intptr_t local_dummy;
#ifdef AMD64
__asm__ volatile ("movq 0(%%rsp), %0" : "=r" (local_dummy) : : "memory");
#else
__asm__ volatile ("movl 0(%%esp),%0" : "=r" (local_dummy) : : "memory");
#endif // AMD64
}
inline void OrderAccess::release() {
// Avoid hitting the same cache-line from
// different threads.
volatile jint local_dummy = 0;
}
inline void OrderAccess::fence() {
if (os::is_MP()) {
// always use locked addl since mfence is sometimes expensive
#ifdef AMD64
__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
}
}
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad | Load1;LoadLoad;Load2; | 保证 load1 的读操作在 load2 及后续读操作之前执行 |
StoreStore | Store1;StoreStore;Store2; | 在 store2 及其后的写操作执行前,保证 store1 的写操作已经刷新到主内存中 |
LoadStore | Load1;LoadStore;Store2; | 在 store2 及其后的写操作执行前,保证 load1 的读操作已读取结束 |
StoreLoad | Store1;StoreLoad;Load2 | 保证 store1 的写操作已刷新到主内存之后,load2 以及其后的读操作才能执行 |
核心思想==>禁止指令重排序 实现方式==>内存屏障
保证了 volatile 读之后的操作不会被重排到 volatile 读之前
保证了 volatile 写之前的操作不会被重排序到 volatile 写之后
操作 1 | 操作 2-普通读写 | 操作 2-volatile 读 | 操作 2-volatile 写 |
---|---|---|---|
普通读写 | 可以重排 | 可以重排 | 不可重排 |
volatile 读 | 不可重排 | 不可重排 | 不可重排 |
volatile 写 | 可以重排 | 不可重排 | 不可重排 |
StoreStore
可以保证在 volatile 写之前,前面的所有普通写操作都刷新至主内存中StoreLoad
避免 volatile 写与其后的 volatile 读/写重排序什么是 volatile 的可见性?
static boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t -----come in");
while (flag) {
}
System.out.println(Thread.currentThread().getName() + "\t -----flag被设置为false,程序停止");
}, "t1").start();
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
System.out.println(Thread.currentThread().getName() + "\t 修改完成flag: " + flag);
}
static volatile boolean flag = true;
上述 6 条只能保证单条指令的原子性,针对多条指令的组合性原子保证,没有大量加锁,因此,JVM 提供了另外两条原子指令
资源类中,一个普通的 int 变量,synchronized 修饰的自增方法 10 个线程并发修改
class MyNumber {
int number;
public synchronized void addPlusPlus() {
number++;
}
}
public static void main(String[] args) {
MyNumber myNumber = new MyNumber();
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
myNumber.addPlusPlus();
}
}, String.valueOf(i)).start();
}
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(myNumber.number);
}
//volatile变量的复合操作不具备原子性
volatile int number;
public void addPlusPlus() {
number++;
}
读取赋值一个普通变量的情况
不保证原子性
从 i++的字节码角度说明
n++;
不具备原子性,若第二个线程在第一个线程读取旧值和写回新值期间读取 n 的域值,则两个线程均会对相同的 n 执行+1 操作,其结果就是+2 的操作volatile 变量不适合参与到依赖当前值的运算(i=i+1;/i++;等)
由于 volatile 变量只能保证可见性,在不符合以下两条规定的运算场景中,仍需要使用 synchronized 或是 java.util.concurrent 中的锁或者原子类,来保证原子性
JVM 字节码角度,n++底层指令角度,间隙期不同步非原子操作(n++)
详见上文《Java 内存模型 JMM》JMM 有序性
volatile int a = 0;
volatile boolean flag = false;
3.4 如何保证可见性?
案例
/**
* 使用 : 当读多于写,结合使用内部锁和volatile变量来减少同步的开销
* 理由 : 利用volatile保证读取操作的可见性,利用synchronized保证复合操作的原子性
*/
private volatile int value;
public int getValue() {
return value; //利用volatile保证读取操作的可见性
}
public synchronized int increment() {
return this.value++; //利用synchronized保证复合操作的原子性
}
public class SafeDoubleCheckSingleton {
private static SafeDoubleCheckSingleton safeDoubleCheckSingleton;
/**
* 私有构造方法
*/
private SafeDoubleCheckSingleton() {
}
/**
* 双重锁设计
*/
public static SafeDoubleCheckSingleton getInstance(){
if (safeDoubleCheckSingleton==null) {
synchronized (SafeDoubleCheckSingleton.class){
if (safeDoubleCheckSingleton==null){
//存在隐患==>多线程环境下,由于重排序,该对象可能未完成初始化,就被其它线程所读取
safeDoubleCheckSingleton = new SafeDoubleCheckSingleton();
}
}
}
//对象不为空,已存在或是创建完毕,执行getInstance不需要获取锁,直接返回对象
return safeDoubleCheckSingleton;
}
}
memory = allcate(); //1.分配对象内存空间
ctorInstance(memory); //2.初始化对象
instance = memory; //3.设置instance指向所分配的内存地址
memory = allcate(); //1.分配对象内存空间
instance = memory; //3.设置instance指向所分配的内存地址
//此时对象还未进行初始化
ctorInstance(memory); //2.初始化对象
添加 volatile 修饰
//通过volatile 声明实现线程安全的延迟初始化
private volatile static SafeDoubleCheckSingleton safeDoubleCheckSingleton;
/**
* 私有构造方法
*/
private SafeDoubleCheckSingleton() {
}
/**
* 双重锁设计
*/
public static SafeDoubleCheckSingleton getInstance(){
//首次检查
if (safeDoubleCheckSingleton==null) {
synchronized (SafeDoubleCheckSingleton.class){
//第二次检查
if (safeDoubleCheckSingleton==null){
//存在隐患==>多线程环境下,由于重排序,该对象可能未完成初始化,就被其它线程所读取
safeDoubleCheckSingleton = new SafeDoubleCheckSingleton();
//解决隐患的原理: 利用volatile,禁止"初始化对象"(2)和"设置singleton指向内存空间"(3)的重排序
}
}
}
//对象不为空,已存在或是创建完毕,执行getInstance不需要获取锁,直接返回对象
return safeDoubleCheckSingleton;
}
内存屏障 : 是一种屏障指令,使得 CPU 或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约束,也称为内存栅栏或栅栏指令