jvm-简介

这里有几个概念可能需要强调:

jvm:java virtual machine,即java虚拟机,可以看成是一个抽象的物理计算机。jvm运行时数据区又分为heap、stack、native method stack、method area(jdk8之前,Hotspot使用Perm Gen实现方法区,该区域在jvm数据区内,jdk8开始使用Metadata实现方法区,该区域在native memory)、pc五大部分,jvm执行引擎负责执行由classloader加载进方法区的class字节码序列。

jmm:java memory model,即java内存模型,与jvm区分,无法相互等价。

jre:Java Runtime Environment,即java运行环境,是一个软件包。jre提供了运行java程序时所必须的环境,比如jvm、java基础类库等。

jdk:Java Development Kit,即java开发套件。jdk是jre的超集,额外包含了一些利于开发的工具,比如javac、javap等。

下面我来说jvm

 JVM

 jdk7中的jvm架构大体如下图:

其中与我们关系最密切莫过于heap和stack了,也就是我们常说的堆栈

堆为共享数据的存储提供了空间,栈为程序的执行了空间(程序执行是需要空间的,比如局部变量)。共享数据和多线程就可能产生线程安全问题,比如我们通过单例模式构造出的实例可以被不同线程使用,如果该共享实例存在线程安全问题(比如有未被保护的可修改的实例变量),就引出了线程同步的问题。

 

JMM

多线程问题在java内存模型(java memory model)中有所体现,如同物理机有高速缓存、内存、缓存一致性协议(用于保证多线程的缓存中数据一致),java虚拟机也有工作内存、主内存、一致性协议(load、store)。

如下图所示,线程A与线程B并发访问共享变量S,当线程A首次访问S时,会先从主内存中得到S的副本S1,在首次对S1中变量进行写入时,可能只会写入缓存(工作内存,注意这里不是jvm运行时数据区中的栈)。如果A利用了缓存(本地内存A),那么线程B在读取S中线程A修改过的变量时就会出现问题,因为此时线程A只更新了其自己的缓存(本地内存A),还未更新在主内存中对应的值,B读到的就是旧值。

(JMM控制就是通过write、read控制工作内存与主内存的一致性)

如何避免这种情况呢?一种方式就是将S中的共享属性修饰为volatile,该关键字表示对被修饰字段的修改必须同步回主内存(而不能只写入缓存),对被修饰字段的读取必须访问主内存(而不能从缓存中读取),即volatile的可见性(volatile还有一个禁止指令重排序的特性)。

 说到JMM就不得不提一下jvm中的八个原子操作:

lock、unlock,作用于主内存,对共享变量加锁以实现线程同步(synchronized就是通过lock、unlock实现线程同步的);

read、load,前者作用于主内存,将变量传递给工作内存,后者作用于工作内存,用于接收主内存传来的变量,并将变量存于工作内存的变量副本中;

store、write,前者作用于工作内存,用于将变量传给主内存,后者作用于主内存,用于接收工作内存传来的变量,并将值更新置主内存;

assign、use,二者都作用于工作内存,前者用于接收执行引擎传来的值,并赋给工作内存中相应的变量,后者用于将工作内存中变量值传给执行引擎。(线程内更新共享变量后,可能只是进行了assign操作,而没有执行store、write操作,即只更新了工作内存中的变量值,而没有同步到主内存中)

由于有一致性协议的存在,store、load不会单独执行,必然会与write、read顺序执行(并不保证连续执行,但相对顺序是保证的,比如read a,read b,load a,load b,而不会read a,read b,load b,load a)。这里就有一个疑问了,在jvm看来一个简单的赋值操作int a = 1;需要这么多个原子操作,而赋值操作本身不是原子操作了?(原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断)待解决

类似的疑问:https://stackoverflow.com/questions/4756536/what-operations-in-java-are-considered-atomic

JMM的三大主题

JMM对于并发的处理,主要就是围绕着3个主题:原子性、可见性、有序性,原子性和有序性是针对操作的,可见性是针对数据的。

原子性,大体可以认为java中对数据的读写是原子操作(long和double未必),如果有更大范围的原子性要求,可以使用synchronized、jdk中的ReentrantLock、Atomic类。

可见性,可见性是指线程A对数据的更新,线程B立即可见。由于缓存的存在,一般操作都是不满足可见性的,但可以通过volatile(读写都会刷新缓存,直接从主内存中取)、final(在this没有逃逸的情况下保证可见性)、synchronized(unlock时会将缓存数据刷回主内存)实现。

有序性,volatile通过禁止指令重排序实现,synchronized通过临界区只允许一个线程访问实现。

这里解释一下final的可见性,比如下段代码,线程A正在MyObject.getInstance,到new MyObject()后线程B开始执行,B调用MyObject.getInstance().getD(),问题就在这里。由于指令重排序问题,obj = new MyObject();的执行顺序可能是分配空间、返回引用、赋初值,那么B就可以通过调用MyObject.getInstance()返回MyObject非空实例(未初始化),但MyObject.getInstance().getD()会返回null。一种解决方法就是将d声明为final,保证了new MyObject返回后d一定是可见的,但如果此时出现this逃逸情况就不一定了。

 1 public MyObject{
 2     private static MyObect obj;
 3     private Date d = new Data();
 4     public Data getD(){return this.d;}
 5     public static MyObect  getInstance(){
 6         if(obj == null){
 7             synchronized(MyObect .class){
 8                 if(obj == null)
 9                     obj = new MyObject();
10                     //出让cpu控制权
11             }
12         }
13         return obj;
14     }
15 }    

 

参考:http://blog.csdn.net/suifeng3051/article/details/52611310

posted @ 2017-07-28 22:43  holoyong  阅读(507)  评论(0编辑  收藏  举报