本文首发于公众号:老胡码字java
在介绍Java内存模型以前,咱们先介绍一下计算机硬件的内存模型,由于JVM的并发和物理机器的并发很类似,甚至JVM并发操做中不少设计都是由于计算机系统的设计引起的。编程
你们都知道计算机系统处理任务主要是靠处理器(CPU)来进行运算的,而运算中又会涉及到数据,数据在哪呢,数据天然是存储在计算机内存中,因此处理器在运算过程当中不可避免的会涉及到与内存的读写交互,好比读取运算所需的数据,存储运算获得的数据结果等。而处理器的运算速度相比物理内存的读写速度要快得多,因此会出现处理器要等待内存数据读写结束后才能进行下一步的运算,所以为了提升计算机的运算速度,如今的计算机系统为处理器添加了一层读写速度尽可能接近处理器的高速缓存来缓解内存与处理器之间的性能差别。这样在处理任务时将运算须要的数据复制到缓存中,运算结束后再将数据从缓存中同步写回到内存,这样处理器在运算时就不须要等待内存数据读写结束了。数组
处理器、高速缓存、内存之间的交互关系图以下:缓存
如上图所示,在多处理器系统中由于每一个处理器都有本身的高速缓存,因此这就引起了一个新的问题,若是多个处理器的运算任务都涉及同一块内存区域,就可能致使各自的高速缓存数据不一致,那这个时候从高速缓存写回主内存的数据以谁为准呢?这就是引入高速缓存引起的新问题,咱们称之为:缓存一致性。安全
为了解决缓存一致性的问题,现代计算机系统须要各个处理器读写缓存时遵循一些协议(MSI、MESI、MOSI、Synapse、Firefly、DragonProtocal,这些都是缓存协议),按照协议来进行读写访问缓存。bash
既然这里说的是“硬件的内存模型”,那什么是内存模型呢?多线程
内存模型能够理解为在特定的操做协议下,对特定的内存和高速缓存进行读写访问的抽象。不一样的物理机器,可能有着不一样的“内存模型”。并发
除了为处理器增长高速缓存以外,处理器还会对输入的代码程序进行乱序执行优化,保证该乱序执行以后的结果和顺序执行的结果一致。举个例子:性能
int a = 1;
int b = 2;
int c = a + b;
复制代码
上面的这段代码将第一行和第二行调换顺序对最终的结果没有任何的影响。而处理器在实际运算过程当中为了优化性能,也会对代码的执行顺序进行相似的调换(保证结果不变的前提下),这种执行顺序的调换称之为指令重排序,而JVM中也存在相似的指令重排序优化功能。至于为何指令重排序会优化性能,它是如何优化性能的,这就涉及到汇编指令的知识,我也不懂汇编指令,这里就不介绍了,有兴趣的能够本身去了解了解。优化
前面说过不一样的物理机器,可能有着不一样的“内存模型”,而Java虚拟机中定义的内存模型能够屏蔽不一样的硬件内存模型,这样就能够保证Java程序在各个平台都能达到一致的内存访问效果,也就是常说的一次编写处处运行,由于内存模型为咱们屏蔽掉了不一样硬件平台之间的差别。
Java内存模型中规定全部变量都存储在主内存(虚拟机内存的一部分)中,主要对应Java的堆内存。这里提到的变量其实是指共享变量,存在线程间竞争的变量,如:实例变量、静态变量和构成数组对象的元素,而局部变量和方法参数由于是线程私有的,因此不存在线程间共享和竞争关系,因此也就不在前面提到的变量范围内。
每一个线程有着本身独有的工做内存,工做内存中保存了被该线程使用到的变量,这些变量来自主内存变量的副本拷贝。线程对变量的全部读写操做都必须在工做内存中进行,不能直接读写主内存中的变量。而不一样线程间的工做内存也是独立的,一个线程没法访问其余线程的工做内存中的变量。
线程工做时,把须要的变量从主内存中拷贝到本身的工做内存,线程运行结束以后再将本身工做内存中的变量写回到主内存中,而多个线程间对变量的交互只能经过主内存来间接实现。具体的线程、工做内存、主内存的交互关系图以下:
经过上面的图和前面的介绍,咱们就很容易明白咱们日常所说的多线程编程时遇到数据状态不一致的问题是怎么产生的。例如:线程1和线程2都须要操做主内存中的共享变量A,当线程1已经在工做内存中修改了共享变量A副本的值可是尚未写回主内存,这时线程2拷贝了主内存中共享变量A到本身的工做内存中,紧接着线程1将本身工做内存中修改过的共享变量A的副本写回到了主内存,很明显线程2加载的共享变量A是以前的旧状态的数据,这样就产生了数据状态不一致的问题。
你们看前面的Java内存模型交互图和硬件内存模型交互图能够发现两种内存模型实际上是很类似的,实际上Java程序在运行过程当中,最终仍是会映射到具体的硬件处理器内核上,但java内存模型和硬件的内存模型并不彻底一致。
对于硬件内存来讲只有寄存器、高速缓存、主内存的概念,并无工做内存(线程私有数据区)和主内存(JVM堆内存)之分,它们只是java内存模型的一种抽象概念并非实际存在的,所以java内存模型对内存的划分对硬件内存并无任何影响。
在java内存模型中,不管是工做内存仍是主内存,它们都有可能存储到硬件的主内存、高速缓存或者是寄存器中,因此java内存模型和硬件内存模型是是一种抽象概念和真实物理硬件的交叉关系。关系图以下:
前面说到工做内存与主内存会进行数据读写交互,这个读写交互具体实现细节则是由Java内存模型来控制的,Java内存模型为主内存和工做内存间的变量拷贝及同步写回定义了具体的实现协议,该协议主要由8种操做来完成,不一样虚拟机在实现时必须保证每个基本数据类型的操做都是原子性不可再分的(long,double类型的变量在某些平台能够例外,虽然在JVM规范中没有强制要求long,double类型具备原子性,可是规范建议各JVM实现成具备原子性的,实际上市面上的JVM也基本都实现了原子性),具体8种操做以下:
线程、工做内存、主内存对应这8种操做的交互关系图以下:
按照上面的8种内存交互操做,若是要把一个变量从主内存复制到工做内存,就须要顺序的执行read和load操做,而若是要把一个变量从工做内存同步回主内存,则须要顺序执行store和write操做,这里说的是顺序执行,而不是连续执行,这也就意味着两个操做之间能够插入其余操做,例如对主内存中的变量1和变量2访问时,一种可能的顺序是read 1, read 2, load 2, load 1。
除此以外,Java内存模型对这8中操做还存在着其余的约束:
Java内存模型其实一直是围绕着并发过程当中的如何处理原子性、可见性和有序性这三个特征创建的。
什么是原子性呢,原子性是指一个操做不可中断,不可分割,在多线程中就是指一旦一个线程开始执行某个操做,就不能被其余线程干扰。
Java内存模型直接用来保证原子性变量的操做包括use、read、load、assign、store、write,咱们大体能够认为Java基本数据类型的访问都是原子性的(long,double除外,前面已经介绍过了),若是用户要操做一个更大的范围保证原子性,Java内存模型还提供了lock和unlock来知足这种需求,可是这两种操做没有直接开放给用户,而是提供了两个更高层次的字节码指令:monitorenter 和 moniterexit,这两个指令对应到Java代码中就是synchronized关键字,因此synchronized代码块之间的操做具备原子性。
可见性是指当一个线程修改了变量以后,其余线程能马上得知这个修改。
Java内存模型经过将变量修改后将新值同步写回主内存,在读取前从主内存刷新变量值,因此JVM内存模型是经过主内存做为传递介质来实现可见性的。不管是普通变量仍是volatile修饰的变量都是这样的,惟一的区别就是volatile变量在被修改以后会马上写回主内存,而在读取时都会从新去主内存读取最新的值,而普通变量则在被修改后会先存储在工做内存,以后再从工做内存写回主内存,而读的时候则是从工做内存中读取该变量的副本拷贝。
除了volatile能够实现可见性以外,synchronized和final关键字也能实现可见性。synchronized同步块的可见性是由于对一个变量执行unlock操做以前,必须将变量的改动写回主内存来(store、write两个操做)实现的。而final字段则是由于一旦final字段初始化完成,其余线程就能够访问final字段的值,并且final字段初始化完成以后就再也不可变。
前面说过处理器在执行运算的时候,会对程序代码进行乱序执行优化,也叫作重排序优化。一样的,在JVM中也存在指令重排序优化,这种优化在单线程中是不会存在问题的,但若是这种优化出如今多线程环境中,就可能会出现多线程安全的问题,由于线程1的指令优化可能影响线程2中某个状态。
Java提供了volatile和synchronized关键字来保证线程间操做的有序性。volatile是由于其自己的禁止指令重排序语义来实现的,而synchronized则是由“同一个变量在同一时刻只能有一个线程对其进行lock操做”这条规则来实现的,这也就是synchronized代码块对同一个锁只能串行进入的缘由。
上面介绍了Java内存模型的3中特性,咱们能够发现synchronized能够说是万能的,它能实现Java多线程中的这3大特性,因此这也早就了不少人在遇到多线程并发操做事都是直接使用synchronized完成,但使用synchronized内置锁会阻塞须要而又没有获取该内置锁的线程,而Java中的线程与操做系统中的原生线程是一一对应的,因此当synchronized内置锁致使某个线程阻塞后,会致使系统从用户态切换到内核态执行阻塞操做,这个操做是很是耗时的。
关于Java内存模型就暂时介绍到这里,接下来的一篇文章会接着介绍更加轻量级的同步实现:volatile关键字,同时还会介绍volatile实现中涉及到的内存屏障。