java并发编程 笔记十四

volatile 原理

volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)

  • 对 volatile 变量的写指令后会加入写屏障
  • 对 volatile 变量的读指令前会加入读屏障

1. 如何保证可见性

  • 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
public void actor2(I_Result r) {
     
 	num = 2;
 	ready = true; // ready 是 volatile 赋值带写屏障
 	// 写屏障
}
  • 而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
public void actor1(I_Result r) {
     
 	// 读屏障
 	// ready 是 volatile 读取值带读屏障
 	if(ready) {
     
 	r.r1 = num + num;
 	} else {
     
 	r.r1 = 1;
 	}
}

java并发编程 笔记十四_第1张图片

2. 如何保证有序性

  • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
public void actor2(I_Result r) {
     
 	num = 2;
 	ready = true; // ready 是 volatile 赋值带写屏障
 	// 写屏障
}
  • 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
public void actor1(I_Result r) {
     
 	// 读屏障
 	// ready 是 volatile 读取值带读屏障
 	if(ready) {
     
	 	r.r1 = num + num;
	 } else {
     
 		r.r1 = 1;
	 }
}

java并发编程 笔记十四_第2张图片
还是那句话,不能解决指令交错:

  • 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
  • 而有序性的保证也只是保证了本线程内相关代码不被重排序
    java并发编程 笔记十四_第3张图片

3. double-checked locking 问题

代码如下
java并发编程 笔记十四_第4张图片
关键在于 0: getstatic 这行代码在 monitor 控制之外,它就像之前举例中不守规则的人,可以越过 monitor 读取INSTANCE 变量的值
这时 t1 还未完全将构造方法执行完毕,如果在构造方法中要执行很多初始化操作,那么 t2 拿到的是将是一个未初始化完毕的单例
对 INSTANCE 使用 volatile 修饰即可,可以禁用指令重排,但要注意在 JDK 5 以上的版本的 volatile 才会真正有效

4. double-checked locking 解决

加了volatile

public final class Singleton {
     
    private Singleton() {
      }
    private static volatile Singleton INSTANCE = null;
    public static Singleton getInstance() {
     
        // 实例没创建,才会进入内部的 synchronized代码块
        if (INSTANCE == null) {
     
            synchronized (Singleton.class) {
      // t2
                // 也许有其它线程已经创建实例,所以再判断一次
                if (INSTANCE == null) {
      // t1
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

如上面的注释内容所示,读写 volatile 变量时会加入内存屏障(Memory Barrier(Memory Fence)),保证下面两点:

  • 可见性
    • 写屏障(sfence)保证在该屏障之前的 t1 对共享变量的改动,都同步到主存当中
    • 而读屏障(lfence)保证在该屏障之后 t2 对共享变量的读取,加载的是主存中最新数据
  • 有序性
    • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
    • 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
  • 更底层是读写变量时使用 lock 指令来多核 CPU 之间的可见性与有序性

java并发编程 笔记十四_第5张图片

你可能感兴趣的:(并发编程,java)