【JVM】Java内存模型

2017-07-04  本文已影响52人  maxwellyue

Java内存模型,即JMM(Java Memery Model),它的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。Java虚拟机规范试图通过定义这样一种模型来屏蔽各种硬件和操作系统的内存访问差异。

此处的变量是指包括实例字段、静态字段和构成数组对象的元素,但不包括局部变量和方法参数,因为后者是线程私有的,不会被共享,不存在竞争问题。


主内存与工作内存

主内存和工作内存.png

内存间交互操作

Java内存模型中定义了8种操作来完成主内存和工作内存之间的交互:如何将变量从主内存拷贝到工作内存、如何将变量从工作内存同步回主内存。

这8中操作每一个都是原子性的、不可再分的。

操作 变量域 作用
lock 主内存 将变量标识为一条线程独占的状态
unlock 主内存 将处于锁定状态的变量释放,之后才可以再被其他线程锁定
read 主内存 将变量值从主内存传输到工作内存
load 工作内存 将read操作从主内存得到的变量放入工作内存的变量副本中
use 工作内存 将工作内存中的变量的值传递给执行引擎使用
assign 工作内存 把从执行引擎中接受到的值赋给工作内存
store 工作内存 将工作内存中变量的值传输到主内存中
write 主内存 将store操作从工作内存中得到的变量的值放入主内存中的变量中

假如主内存中存在变量var,工作内存中存在变量var的副本var_copy:先通过对var执行read操作,将var的值传输到工作内存中;再对var_copy执行load操作将传来的值赋给var_copy;当执行引擎执行使用到该变量的字节码指令时,对var_copy执行use操作将var_copy的值传输给执行引擎;执行引擎执行结束后,对var_copy执行assign操作,将执行引擎传输来的值赋给var_copy;对var_copy执行store操作,将var_copy的值传输到主内存,对var执行write操作,将传输来的值赋给var

执行上述8种操作时必须满足如下8种规则:


原子性、可见性、有序性

Java内存模型是围绕着在并发过程中如何处理原子性、可见性、有序性这三个特征来建立的。

比如a=5这个给a赋值的操作时原子性的,但a = a + 1就不是原子性的;

但是,在实际应用中,通常需要更大范围的原子性保证,比如上面的a = a + 1操作。Java内存模型提供了lock和unlock来满足这种需求,虽然这两个操作并未开放给用户使用,但却提供了更高层次的字节码指令monitorenter和monitorexit来隐士地使用这两个操作。这两个字节码指令反映到Java代码中就是同步块——synchronized关键字。因此在synchronized块之间的操作也具备原子性。


volatile型变量的特殊规则

Java内存模型对volatile专门定义了一些特殊的访问规则。

通过上述的访问规则可以看出,volatile的实现(或者说volatile的同步机制)是没有用到lock操作的(无锁)。

正是由于上述特殊的访问规则,才使得volatile关键字修饰的变量具有如下两个特征:

volatile boolean shouldStop = false;
public void stop(){
      shouldStop = true;
}    
public void doWork(){
      while (!shouldStop){
            //do something
      }
}
//
volatile boolean isInitialized = false;
//
//假设以下代码在线程A中执行
void initial(){
    //此处是加载配置文件的代码
    isInitialized = true;
}
//
//假设以下代码在线程B中执行,此方法依赖线程A中加载完配置文件
void doWork(){
    while (isInitialized){
        //do something 
    }
}

如果变量isInitialized没有volatile修饰,有可能线程A中的initial()方法会先执行isInitialized = true;,再执行加载配置文件的代码,就会导致线程B在执行doWork()方法的时候报错。


先行发生原则

注意:一个操作时间上的先发生不代表这个操作会先行发生,同样,一个先行发生的操作不代表时间上的先发生(指令重排序等)。

public class HappensBefore {

    private int value = 0;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

上述代码中,如果线程A先(时间上的先)调用了setValue(1)方法,线程B后(时间上的后)调用了getValue(),那么B有可能获取的value值是0,而不一定是1。


内容摘抄自《深入理解Java虚拟机》

上一篇 下一篇

猜你喜欢

热点阅读