理解JVM之java内存模型

  java虚拟机规范中试图定义一种java内存模型(JMM)来屏蔽掉各类硬件和操做系统内存访问差别,以实现让java程序在各类平台都能打到一致的内存访问效果.因此java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层实现细节.注意,这里的变量是包括了实例字段,众泰字段,构成数组对象的元素,但不包括局部变量和方法参数,由于后者是线程私有的,不会被共享.java

1.主内存和工做内存数组

  JMM规定了全部的变量都存储在主内存中,每条线程还有本身的工做内存.工做内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的全部操做都必须在工做内存中进行,而不能直接读写主内存的变量,工做内存是线程私有的.这里指的主内存和工做内存,和java的虚拟机的内存划分不是同一个层次的划分,二者不对等,若是非要对等起来,主内存应该对于java堆中对象的数据区域,工做内存对于线程私有的虚拟机栈的一部分.安全

2.主内存与工做内存的交互ide

  主内存与工做内存之间如何交互,java内存模型定义了如下8个操做,每一个操做都是原子的,不可再分的(对于double,long可能有例外,其在有些平台上会以32位为单位读取)函数

  1) lock:做用于主内存中的变量,它将一个变量标志为一个线程独占的状态。 spa

  2) unlock:做用于主内存中的变量,解除变量的锁定状态,被解除锁定状态的变量才能被其余线程锁定。 操作系统

  3) read:做用于主内存中的变量,它把一个变量的值从主内存中传递到工做内存,以便进行下一步的load操做。 线程

  4) load:做用于工做内存中的变量,它把read操做传递来的变量值放到工做内存中的变量副本中。 code

  5) use:做用于工做内存中的变量,这个操做把变量副本中的值传递给执行引擎。当执行须要使用到变量值的字节码指令的时候就会执行这个操做。 对象

  6) assign:做用于工做内存中的变量,接收执行引擎传递过来的值,将其赋给工做内存中的变量。当执行赋值的字节码指令的时候就会执行这个操做。

  7) store:做用于工做内存中的变量,它把工做内存中的值传递到主内存中来,以便进行下一步write操做。 

  8) write:做用于主内存中的变量,它把store传递过来的值放到主内存的变量中。

  在将变量从主内存读取到工做内存中,必须顺序执行read、load;要将变量从工做内存同步回主内存中,必须顺序执行store、write。而且这8种操做必须遵循如下规则: 
  1) 不容许read和load、store和write操做之一单独出现。即不容许一个变量从主内存被读取了,可是工做内存不接受,或者从工做内存回写了可是主内存不接受。 

  2) 不容许一个线程丢弃它最近的一个assign操做,即变量在工做内存被更改后必须同步改更改回主内存。 

  3) 工做内存中的变量在没有执行过assign操做时,不容许无心义的同步回主内存。 

  4) 在执行use前必须已执行load,在执行store前必须已执行assign。 

  5) 一个变量在同一时刻只容许一个线程对其执行lock操做,一个线程能够对同一个变量执行屡次lock,但必须执行相同次数的unlock操做才可解锁。 

  6) 一个线程在lock一个变量的时候,将会清空工做内存中的此变量的值,执行引擎在use前必须从新read和load。 

  7) 线程不容许unlock其余线程的lock操做。而且unlock操做必须是在本线程的lock操做以后。 

  8) 在执行unlock以前,必须首先执行了store和write操做。

  对于普通变量来讲,他的读写操做以下:

  变量读:

  1) 进入同步块时,同步块中读变量的值时,将会从主存中刷新到工做区,读到最新值
  2) 读volatile变量时,将会从主存中刷新到工做区,读到最新值;
  3) 读普通变量时,若是跟随在读volatile变量以后,将会从主存刷新到工做区,读到最新值
  4) 读final变量时,若是未初始化完成,则将等待final变量完成初始化,并获取主存中的最新值
  5) 新建线程中,变量的值将会从主存刷新到工做区,读到最新值
  变量写:
  1) 退出同步块时,同步块中变量的赋值被强制刷新到主存;
  2) volatile变量的赋值,被强制刷新到主存,时机在读volatile变量以前;
  3) 普通变量随同volatile变量,在同一个线程中的赋值,将跟随volatile变量被刷新到主存;
  4) final变量的赋值,在读final变量以前将被强制刷新到主存;
  5) 单独的普通变量的赋值,将在线程结束以前被刷新到主存;

3.volatitle变量的特殊规则

  volatile是java虚拟机提供的最轻量级的同步机制,volatile变量具备两个特性:一个是保证此变量对全部线程的可见性,即若是该变量的值被修改,这个新值对于其余线程来讲是当即得知的.另外一个是volatile会阻止指令重排序.因为volatile变量第一个特性,致使不少人认为volatile变量的是绝对的线程安全的,这个想法是有问题的,好比下面这个代码

 1 private static volatile int a=0;  2     
 3     private static void increase(){  4         a++;  5  }  6     
 7     public static void main(String[] args) {  8         Thread[] threads=new Thread[20];  9         for(int i=0;i<20;i++){ 10             threads[i]=new Thread(new Runnable() { 11  @Override 12                 public void run() { 13                     for(int i=0;i<10000;i++) 14  increase(); 15  } 16  }); 17  threads[i].start(); 18  } 19         while(Thread.activeCount()>1) 20  Thread.yield(); 21  System.out.println(a); 22     }

  这里开启了20个线程,每一个线程对a变量执行10000次加一操做,若是volatile变量是绝对的线程安全的,那么这个程序输出的结果必然为200000,可是这个程序输出的结果基本上是低于200000的.缘由是,实现volatile变量的可见性的方法其实是线程每次读这个变量的时候都会从主内存中读取这个值,每次写这个变量时写完会强制刷新到主内存中.在一个线程读取了这个值以后,另外一个线程修改了主内存中的值,那么前一个线程的值就变成了过时的值,因此结果会出现偏差.

  因为volatile变量只能保证可见性,因此在一些状况下仍是须要加锁来保证原子性,在如下状况下可以不加锁使用volatile变量来保证原子性:

  1) 运算结果并不依赖变量当前的值,或者确保只有单个线程能够更新变量的值.

  2) 变量不须要与其余的状态变量共同参与不变约束

4.java内存模型的特征

  1) 原子性:java内存模型直接保证原子性变量操做的包括read,load,assign,use,store,write,大体的能够认为基本数据类型的读写是具有原子性的(double跟long除外,开头已经说了)

实现大范围的原子性可以使用lock与unlock操做.

  2) 可见性:当一个线程修改了共享变量的值其余线程可以当即得知这个修改,变量如何实现可见性前面已经提到.

  3) 有序性:java程序中自然的有序性能够总结为:若是本线程内观察,全部操做都是有序的,若是一个线程中观察另外一个线程,全部操做都是无序的.

5.先行先发生原则

  先行发生原则--是判断是否存在数据竞争、线程是否安全的主要依据。

  先行发生是Java内存模型中定义的两项操做之间的偏序关系。若是说操做A先行发生于操做B,其实就是说在发生操做B以前,操做A产生的影响被操做B察觉。

  下面是JAVA内存模型下一些自然的先行发生关系,不须要热河同步器协助就已经存在,虚拟机能够对他门进行重排序.

  一、程序次序规则:在一个线程内,书写在前面的代码先行发生于后面的。确切地说应该是,按照程序的控制流顺序,由于存在一些分支结构。

  二、Volatile变量规则:对一个volatile修饰的变量,对他的写操做先行发生于读操做.

  三、线程启动规则:Thread对象的start()方法先行发生于此线程的每个动做.

  四、线程终止规则:线程的全部操做都先行发生于对此线程的终止检测.

  五、线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码所检测到的中断事件.

  六、对象终止规则:一个对象的初始化完成(构造函数之行结束)先行发生于发的finilize()方法的开始.

  七、传递性:A先行发生B,B先行发生C,那么,A先行发生C.

  八、管程锁定规则:一个unlock操做先行发生于后面对同一个锁的lock操做.

相关文章
相关标签/搜索