Java 内存模型的总结

看了几遍《Java 并发编程的艺术》,对Java内存模型JMM的理解加深了不少,记录一下自己的理解吧。

概念:java线程之间的通信由JMM来控制,决定一个线程对共享变量的修改何时对另外一个线程可见,抽象的说:共享变量存储在主内存中,而每个线程由一份本地内存,存储了共享变量的副本。

如下一张图:

JMM如何控制呢。答案就是JMM有一条规则保证,这套规则就叫Happens-before规则,简单的解释一下这个规则的语义,就是如果一个操作的结果对另外一个操作可见,那么存在happens-before关系。操作可以在一个线程,可以在不同线程。那么这套规则的运作是靠如下几个规则:

1.程序顺序规则,这个简单线程中的每个操作happens-before对任意后续操作

2.监视器锁规则,加锁happens-before释放锁

3.volatile变量规则:对一个volatile域的写happens-before任意后续的读。

4.传递性:如果 A happens-before B,且B happens-before C,那么 A happens-before C。

修复单例的双重校验的并发性问题是典型的happens-before运用。问题的根源在于创建对象的指令发生了重排序。

instance = new Singletong(),分为下面三个操作

memory = allocate();// 1:分配对象的内存空间

ctorInstance(memory);//2:初始化对象

instance = memory;//3:设置instance 指向刚分配的内存地址

2和3发生重排序,所以会发生这种情况。三个步骤编程了1,3,2

public static Instance getInstance(){
  if(instance == null){                   //1
      synchronized (X.class){             //2
          instance = new X();             //3

      }
  }
  return instance;                       //4

}

A线程执行第三布的时候,B线程执行1,因为发生了重排序,instance在没有初始化完成的时候,已经指向了内存,不为null,所以此时B线程的判断instance!=null,然后执行4,返回了没有初始化完成的对象,导致后续的问题。其中有一个方案,就是用volatile 来修饰instance,这样会阻止重排序,简单的说,在初始化第3部,插入一个内存屏障,禁止上面的操作和volatile变量的写重排序,避免B线程读取的问题。这个利用了happens-before的volatile规则。当然解决这个问题,还有另外一种办法,就是利用静态单例类,虽然语法上看不出来,但语义上也是利用了happens-before的监视器锁规则  。

猜你喜欢

转载自blog.csdn.net/hanshengjian/article/details/86687927