happens-before 原则 && volatile 在单例模式中的应用

https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4  

 

Happens-before 的定义

 

 

通过上面的定义,我们得出 happens-before 的原则: 

happens-before 定义了什么时候会发生数据争用(即:多线程读取共享变量)。

happens-before 原则:

1. 对锁(对象监视器)的 释放(unlock)操作 happens-before 锁的 lock 操作

2. 对 volatile 字段的写操作 happens-before 对这个字段的读操作
  从底层实现来看,volatile 写操作会通过汇编中的 lock 前缀指令,对这块内存区域的缓存行进行锁定(相当于加入了内存屏障,从而保证程序执行顺序),
这样,如果此时正在对 volatile 变量时行写操作,那么其他线程所有的读操作都需要等待写操作完成,这就是 volatile 写操作 happens-before 对这个字段读操作的原因
3. 对线程的 start() 的调用 happens-before 这个线程里面的代码的执行 4. t1.join() 成功后,那么 t1 线程里面的代码的执行 happens-before main 线程 5. 一个对象默认的初始化操作 happens-before 这个程序里面的其他操作

 

当一个程序包含两个冲突的访问,且这两个访问没有按照 happens-before 原则进行排序,那么,我们称之为数据争用。

线程间操作以外的操作的语义,例如,读取数组长度、执行已检查的强制转换,以及调用虚拟方法,不会直接影响数据争用。

当且仅当所有顺序一致的执行都没有数据争用时,程序才能正确同步。

如果一个程序正确同步,则该程序的所有执行将看起来是顺序一致的。

这是对程序员的极其有力的保证。程序员无需考虑重排序(指令重排)即可确定其代码包含数据争用。因此,在确定其代码是否正确同步时,他们无需考虑指令重排。一旦确定代码正确同步,程序员就不必担心指令重排会影响他的代码。

一个程序必须正确的同步,从而避免在指令重排时得到意想不到的结果。使用正确的同步不能确保程序的整体行为正确。但是,同步的使用让程序员可以以一种简单的方式来推理程序的可能行为。正确同步的程序的行为很少依赖于可能的指令重排。没有正确同步的程序,就可能得到非常奇怪、令人困惑和违反直觉的结果。

 

 

 

volatile 的应用之——双重校验锁实现单例模式:

这种方式必须使用 volatile 修饰符,以确保程序的正确性,避免指令重排导致获取到一个未被初始化的对象,从而导致程序崩溃。这里使用到了 volatile 可以保证有序性的特性(禁止指令重排)

(volatile 是 jdk 1.5 之后出现的)

 

 1 /**
 2  * 查看字节码
 3  * javap -v -p -s -sysinfo -constants NewTest.class
 4  * javap -v NewTest.class
 5  */
 6 public class NewTest {
 7     public volatile Object o;
 8 
 9     /**
10      * <pre>
11      *     public void m();
12      *        descriptor: ()V
13      *        flags: ACC_PUBLIC
14      *        Code:
15      *        stack=3, locals=1, args_size=1
16      *        0: aload_0
17      *        1: new           #2                  // class java/lang/Object
18      *        4: dup
19      *        5: invokespecial #1                  // Method java/lang/Object."<init>":()V
20      *        8: putfield      #3                  // Field o:Ljava/lang/Object;
21      *        11: return
22      * </pre>
23      */
24     public void m(){
25         // 1. 分配内存
26         // 2. 调用 Object 的构造函数,初始化对象
27         // 3. 将对象内存的引用赋值给变量 o
28         o = new Object();
29     }
30 }

 

参考:

https://en.wikipedia.org/wiki/Double-checked_locking
https://javarevisited.blogspot.com/2014/05/double-checked-locking-on-singleton-in-java.html
infoq: 双重检查锁定与延迟初始化

 

 

posted on 2020-07-13 17:14  快鸟  阅读(262)  评论(0编辑  收藏  举报