标签:logs java程序 执行 共享变量 部分 外包 范围 tom 本地
这里有3个概念可能需要强调:
jvm:java virtual machine,即java虚拟机,可以看成是一个抽象的物理计算机。jvm运行时数据区又分为heap、stack、native method stack、method area、pc五大部分,jvm执行引擎负责执行由classloader加载进方法区的class字节码序列。
jre:Java Runtime Environment,即java运行环境,是一个软件包。jre提供了运行java程序时所必须的环境,比如jvm、java基础类库等。
jdk:Java Development Kit,即java开发套件。jdk是jre的超集,额外包含了一些利于开发的工具,比如javac、javap等。
下面我来说jvm
jdk7中的jvm架构大体如下图:
其中与我们关系最密切莫过于heap和stack了,也就是我们常说的堆栈。
堆为共享数据的存储提供了空间,栈为程序的执行了空间(程序执行是需要空间的,比如局部变量)。共享数据和多线程就可能产生线程安全问题,比如我们通过单例模式构造出的实例可以被不同线程使用,如果该共享实例存在线程安全问题(比如有未被保护的可修改的实例变量),就引出了线程同步的问题。
多线程问题在java内存模型(java memory model)中有所体现,如同物理机有高速缓存、内存、缓存一致性协议(用于保证多线程的缓存中数据一致),java虚拟机也有工作内存、主内存、一致性协议(load、store)。
如下图所示,线程A与线程B并发访问共享变量S,在本地内存(工作内存,注意这里不是jvm运行时数据区中的栈)中会存有共享变量的副本(引用)S1,当线程A首次访问S时,会先从主内存中得到S的引用S1,在首次对S1中变量进行写入时,亦会从主内存中获取(可能还会缓存于本地内存),当再次对S1中相同变量进行写入时,可能就只是对缓存做出相应的操作了。如果A利用了缓存(本地内存A),那么线程B在读取S中线程A修改过的变量时就会出现问题,因为此时线程A只更新了其自己的缓存(本地内存A),还未更新在主内存中对应的值,B读到的就是旧值。
如何避免这种情况呢?一种方式就是将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对于并发的处理,主要就是围绕着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(),问题就在这里。线程A此时并未unlock,也就是说此时obj和d都未必同步回主内存,假设此时obj同步回主内存而d没有(这是一种可能的情况),那么B就可以通过调用MyObject.getInstance()返回MyObject实例,但是由于d还未同步回主内存,故MyObject.getInstance().getD()会返回null。
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 }
标签:logs java程序 执行 共享变量 部分 外包 范围 tom 本地
原文地址:http://www.cnblogs.com/holoyong/p/7251524.html