Java并发编程(四)有序性

Java极客  |  做者  /  铿然一叶
这是Java极客的第 32 篇原创文章

1、指令重排

在计算机执行指令的顺序在通过程序编译器编译以后造成的指令序列,通常而言,这个指令序列是会输出肯定的结果;以确保每一次的执行都有肯定的结果。可是,通常状况下,CPU和编译器为了提高程序执行的效率,会按照必定的规则容许进行指令优化,在某些状况下,这种优化会带来一些执行的逻辑问题,主要的缘由是代码逻辑之间是存在必定的前后顺序,在并发执行状况下,会发生二义性,即按照不一样的执行逻辑,会获得不一样的结果信息。java

代码例子:编程

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (null == singleton) {
            synchronized (Singleton.class) {
                if (null == singleton) {
                    singleton = new Singleton();
                }
            }
        }

        return singleton;
    }
}
复制代码

对于以下代码:缓存

singleton = new Singleton();
复制代码

咱们觉得的顺序是:
1.分配一块内存
2.在内存上初始化Singleton对象
3.而后M的地址赋值给instance变量安全

但实际上不是,查看JAVA字节码:多线程

public class com.javashizhan.concurrent.demo.base.Singleton {
  public static com.javashizhan.concurrent.demo.base.Singleton getInstance();
    Code:
       0: aconst_null
       1: getstatic     #2                  // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
       4: if_acmpne     39
       7: ldc           #3                  // class com/javashizhan/concurrent/demo/base/Singleton
       9: dup
      10: astore_0
      11: monitorenter
      12: aconst_null
      13: getstatic     #2                  // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
      16: if_acmpne     29
      19: new           #3                  // class com/javashizhan/concurrent/demo/base/Singleton
      22: dup
      23: invokespecial #4                  // Method "<init>":()V
      26: putstatic     #2                  // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
      29: aload_0
      30: monitorexit
      31: goto          39
      34: astore_1
      35: aload_0
      36: monitorexit
      37: aload_1
      38: athrow
      39: getstatic     #2                  // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
      42: areturn
    Exception table:
       from    to  target type
          12    31    34   any
          34    37    34   any
}
复制代码

看19~26,实际的顺序是:
1.分配一块内存
2.将M的地址赋值给instance变量
3.最后在内存M上初始化Singleton对象。并发

因为指令的顺序问题,多线程并发时线程A执行到26以前发生了线程切换,此时线程B发现null == singleton不成立,获取到singleton,而此时singleton并无初始化完,就会引起空指针异常。app

2、Happens-Before 规则

Happens-Before 约束了编译器的优化行为,虽容许编译器优化,可是要求编译器优化后必定遵照Happens-Before 规则。函数

1.程序的顺序性规则
在一个线程中,按照程序顺序,前面的操做Happens-Before于后续的任意操做。程序前面对某个变量的修改必定是对后续操做可见的。post

2.volatile变量规则 对一个volatile变量的写操做,Happens-Before于后续对这个volatile变量的读操做。优化

3.传递性 若是A Happens-Before于B,B Happens-Before于C,那么A Happens-Before于C。

4.锁的规则
对一个锁的解锁Happens-Before于对这个锁的加锁。

5.线程Start规则 主线程A启动子线程B后,子线程B能看到主线程在启动子线程B以前的操做。

6.线程join规则
主线程A等待子线程B完成(主线程经过调用子线程B的join方法实现),当子线程B完成后(主线程A中join()方法返回),主线程可以看到子线程的操做。

7.线程中断规则
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,能够经过Thread.interrupted()方法检测到是否有中断发生。

8.对象终结规则
一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

end.


相关阅读:
Java并发编程(一)知识地图
Java并发编程(二)原子性
Java并发编程(三)可见性
Java并发编程(五)建立线程方式概览
Java并发编程入门(六)synchronized用法
Java并发编程入门(七)轻松理解wait和notify以及使用场景
Java并发编程入门(八)线程生命周期
Java并发编程入门(九)死锁和死锁定位
Java并发编程入门(十)锁优化
Java并发编程入门(十一)限流场景和Spring限流器实现
Java并发编程入门(十二)生产者和消费者模式-代码模板
Java并发编程入门(十三)读写锁和缓存模板
Java并发编程入门(十四)CountDownLatch应用场景
Java并发编程入门(十五)CyclicBarrier应用场景
Java并发编程入门(十六)秒懂线程池差异
Java并发编程入门(十七)一图掌握线程经常使用类和接口
Java并发编程入门(十八)再论线程安全


Java极客站点: javageektour.com/

相关文章
相关标签/搜索