Java内存模型(jmm)的出现是为了各类操做系统和硬件的内存访问的差别。
Java内存模型规定了变量(不含局部变量,由于局部变量线程私有,不存在共享问题)都得存放在主内存中,而每一个线程对这些变量的操做都必须是从主内存中取出来并在工做内存中完成(如读取、写入的操做),不一样线程之间不能访问对方的工做内存。以下图,展示了线程、主内存、工做内存之间的交互关系: java
加入一个工做内存的目的很明显,就是为了加快在内存中的操做数据的速度,由于工做内存优先存储在寄存器和高速缓存中,这两个操做的速度都远远快于主内存。 编程
主内存和工做内存之间交互的主要操做为: 缓存
l Lock(锁定):做用于主内存的变量,将主内存该变量标记成当前线程私有的,其余线程没法访问。 安全
l Unlock(解锁):做用于主内存的变量,解除主内存中该变量的锁定状态,让他变成线程共享变量。 多线程
l Read(读取):做用于主内存的变量,将该变量读取到当前线程的工做内存中,以便进行load操做。 并发
l Load(加载):做用于工做内存中的变量,将read获取到的变量载入工做内存的变量副本中。 app
l Use(使用):做用于工做内存中的变量,虚拟机执行引擎在执行字节码指令的时候,碰到了一个变量就会执行该操做,使用该变量。 jvm
l Assgin(赋值):做用于工做内存中的变量,虚拟机执行引擎在执行字节码指令的时候,碰到了变量赋值的指令就会执行该操做。 函数
l Store(存储):做用于工做内存中的变量,将工做内存中的变量放入主内存,以便进行write操做。 性能
l Write(写入):做用于主内存中的变量,将store获得的变量放入主内存的变量中。
很明显,若是两个线程A、B访问同一个变量2的时候,都没有锁定。
A在工做内存改了变量值+1,而B此时并不能看到这个操做,B还觉得没人改动这个值,就认为本身是再原来的值上进行操做-1。算出来的值变成了多少呢?噢,并不知道,由于多是3,也多是1。这时,就出现了并发访问的线程安全问题。
Volatile是jvm提供的最轻量级的同步机制,可是要正确使用不容易。
Volatile能够保证两个线程对变量操做的可见性,即一个线程操做的操做能够被另外一线程看到。可是若是操做不是原子的,依然无法保证volatile同步的正确性。只有在下述状况,才可使用这个关键字:
l 对变量的写入操做不依赖于该变量的当前值(好比a=0;a=a+1的操做,整个流程为a初始化为0,将a的值在0的基础之上加1,而后赋值给a自己,很明显依赖了当前值),或者确保只有单一线程修改变量。
l 该变量不会与其余状态变量归入不变性条件中。(当变量自己是不可变时,volatile能保证安全访问,好比双重判断的单例模式。但一旦其余状态变量参杂进来的时候,并发状况就没法预知,正确性也没法保障)
package org.project.loda.test; /** * * @ClassName: Singleton * @Description: 基于双重判断的单例模式(普遍使用) * @author minjun * @date 2015年6月5日 下午10:20:37 * */ public class Singleton { private static volatile Singleton s; public static Singleton getInstance() { if (s == null) { synchronized (Singleton.class) { if (s == null) { s = new Singleton(); } } } return s; } }
Volatile还有个特性就是,他能够禁止指令进行重排序优化。什么是重排序?看看java并发编程实战中的解释:
各类操做延迟或者看似乱序执行的不一样缘由,均可以归为重排序。这是一个没有间接性解释,由于直接解释很难以理解。下面看看这个程序:
若是按上到下顺序判断,线程one执行顺序为:a=1,x=b,线程other执行顺序应该为:b=1,y=a。而后完成计算,打印其值。可是,事实多是:
个人天哪?x=b居然在a=1以前执行了!!!发生了什么?其实,这就是底层优化多线程程序的时候进行了重排序操做,致使乱序出现。最可怕的是,这还不是必然的,他可能发生,可能不发生,你根本预料不到!
因此,volatile自己强大的地方就是他还能预防这种状况发生,虽然牺牲了一点性能,可是大大加强了程序的可靠性。可是记住,不要依赖于volatile,在合适的时候才使用他(上文已经说明),若是状况不合适,就使用传统的synchronized关键字同步共享变量的访问,用来保证程序正确性(这个关键字的性能会随着jvm不断完善而不断提高,未来性能会慢慢逼近volatile)。
说了这么多,发现可使用volatile和synchronized关键字进行同步。可是,你不会平白无故就使用它们,存在竞争、线程安全问题的时候才应该考虑使用,可是如何判断是否存在这些问题呢?下面介绍java内存模型中的一个重点原则——先行发生原则(Happens-Before),使用这个原则做为依据,来指导你判断是否存在线程安全和竞争问题。
l 程序顺序规则:在程序中,若是A操做在B操做以前(好比A代码在B代码上面,或者由A程序调用B程序),那么在这个线程中,A操做将在B操做以前执行。
l 管理锁定规则:一个unlock操做先于后面对同一个锁的lock操做以前执行。
l Volatile变量规则:对一个volatile变量的写操做必须在对该变量的读操做以前发生。
l 线程启动规则:线程的Thread.start必须在该线程全部其余操做以前发生
l 线程终止规则:线程中全部操做都先行发生于该线程的终止检测。能够经过Thread.join()方法结束、Thread.isAlive()的返回值判断线程是否终止。
l 线程中断规则:对线程interrupt()方法的调用必须在被中断线程的代码检测到interrupt调用以前执行。
l 对象终结规则:对象的初始化(构造函数的调用)必须在该对象的finalize()方法完成。
l 传递性:若是A先行发生于B,B先行发生于C,那么A先行发生于C。
这些操做是无需使用任何同步手段就能保证成立的先行发生规则。若是要线程A、B,须要B能看到A操做的结果(不管二者是否在一个线程当中),须要A、B知足Happens-Before关系,若是两个操做不存在Happens-Before关系,JVM会对他们进行任意重排序。当A和B在同一个线程中,或者两个线程使用一样的锁,他们就能知足Happens-Before,若是使用不一样锁,就不知足。
线程也叫做轻量级进程,是大多现代操做系统的基本调度单位。在同一个进程中,多个线程共享内存空间,所以须要足够的同步机制才能保证正常访问。每一个线程自己都有各自的程序计数器、栈和局部变量等。在java中使用线程调度的方式是抢占式的,须要由操做系统分配执行时间,线程自己没法决定(例如java中,只有Thread.yield()可让出本身的执行时间,可是并无提供能够主动获取执行时间的操做)。虽然java中线程调度由系统执行,可是仍是能够经过设置线程优先级来“建议”操做系统多给某些线程分配执行时间(而后,这并不必定就能保证高优先级的先执行,因此不太靠谱...)。
Java定义了以下几种线程状态,一个线程有且仅有一个:
上图指定了几种状态以及达到这些状态所须要经历的过程,这里就不给出解释了,有基础的同窗应该很容易看懂这些状态。
参考文献:java并发编程实战