计算机的 CPU
和内存之间一直有一个核心矛盾,就是它们之间的运算速度有好几个数量级的差距,为了平衡它们的差别,主要作了如下:数组
CPU
增长了高速缓存,以均衡与内存的速度差别;CPU
,均衡 CPU
与 I/O
设备的差别;虽然高速缓存很好地解决了处理器与内存的速度矛盾,可是又出现了一个新的问题。在多核处理机中,每一个处理器都有本身的高速缓存,它们共享同一主内存。当多个处理器的任务涉及到同一块主内存区域时,可能致使缓存的数据不一致的状况,这就是可见性问题,可见性是指一个线程对共享变量的修改,另一个线程可以马上看到。缓存
操做系统基于线程来进行任务调度。高级语言的一条语句每每须要多条指令完成,可是任务切换能够发生在任何一条 CPU
指令后,在多线程环境下这就可能致使数据与预期的不一致,即原子性问题。原子性是指一个或多个操做在 CPU
执行过程当中不被中断。安全
编译器的指令重排序优化一样不能保证最终的结果与预期的一致。这里的重排序会知足如下两个条件:多线程
as-if-serial
:在单线程环境下无论怎么重排序,不能改变程序运行的结果。须要注意的是:虽然重排序不会影响单线程环境的执行结果,可是会破坏多线程的执行语义。也就是有序性问题,有序性指的是程序按照代码的前后顺序(逻辑前后)执行。并发
因此,JVM
试图虚拟机定义了一种 Java
内存模型(Java Memory Model
,JMM
)来屏蔽掉各层硬件和操做系统的内存访问差别,以实现让 Java
程序在各类平台下都能达到一致的内存访问效果,也就是解决以上三个问题。app
Java
内存模型主要是为了定义程序中各个变量的访问规则,此处的变量指的是实例字段、静态字段和构成数组对象的元素等共享变量。学习
Java
内存模型规定了全部的变量都存储在主内存中。每一个线程还有本身的工做内存,其中保存了该线程使用的变量的主内存副本拷贝,线程对变量的全部操做必须在工做内存中进行。不一样线程之间也没法直接访问对方工做内存中的变量,线程之间的变量值传递须要经过主内存来完成。优化
线程、工做内存、主内存三者的关系以下:this
对于主内存与工做内存之间交互的实现细节,Java
内存模型中定义了 8
种操做来实现,虚拟机实现时必须保证这些操做是原子性的。操作系统
read
(读取):把一个变量的值从主内存传输到工做内存中;load
(载入):把 read
操做从主内存获得的变量放入工做内存的变量副本中;use
(使用);把工做内存中一个变量的值传递给执行引擎;assign
(使用):把一个从执行引擎接收到的值赋给工做内存的变量;store
(存储):把工做内存中一个变量的值传送到主内存中;write
(写入):把 store
操做从工做内存中获得的变量放入主内存的变量中。关键字 volatile
是 JVM
提供的轻量级的同步机制。当一个变量被定义为 volatile
后,它能够保证内存的可见性。
使用 volatile
还能够禁止指令重排序优化。它是 Java
编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。
内存屏障(Memory Barrier
)是一组处理器指令,用于实现对内存访问操做的顺序限制。在重排序时不能把后面的指令重排序到内存屏障以前的位置。
Java
内存模型保证了并发的三个特性:原子性、可见性、有序性,下面学习一下哪些操做实现了这三个特性:
Java
内存模型保证了内存间交互的 8
个操做的原子性,但对于 64
位的数据类型(long
和 double
),容许虚拟机的实现能够不保证 64
位数据类型的 load
、store
、read
和 write
这 4
个操做的原子性。但目前虚拟机几乎都把 64
位数据的读写操纵做为原子性来对待。也就是说能够认为基本类型的读写访问是具有原子性的。
JMM
还提供了 lock
和 unlock
操做来保证更大范围的原子性,尽管虚拟机并未将其开放给用户,但可以使用 monitorenter
和 monitorexit
字节码指令来隐式地使用这两个操做,对应到 Java
代码中就是 synchronized
关键字,因此 synchronized
同步块也是原子性的。
主要有三种方式实现可见性:
volatile
:volatile
保证了新值能当即同步到主内存,以及每次使用前当即从主内存刷新。synchronized
:对同步块加锁解锁,在执行 unlock
操做前必须把此变量值同步到主内存中。final
:被 final
关键字修饰的字段在构造器中一旦初始化完成,而且没有发生 this
引用逃逸(其它线程可能经过引用访问到初始化了一半的对象),那么其它线程就能看见 final
字段的值。Java
中有两种方式保证线程之间操做的有序性;
volatile
关键字经过添加内存屏障的方式来禁止指令重排。synchronized
来保证有序性,它保证每一个时刻只有一个线程执行同步代码,即让线程串行地执行同步代码。前面说的保证并发安全的定义实践起来比较麻烦,有一个等效判断原则——Happens-Before
原则,来肯定一个访问在并发环境下是否安全。
Happens-Before
的含义就是前面一个操做的结果对后续操做是可见的。要想保证执行操做 B
的线程看到线程 A
的结果,那么 A
和 B
之间必须知足 Happens-Before
原则。若是两个操做之间缺少 Happens-Before
原则,那么 JVM
就能够对它们任意地重排序,那么就会产生数据竞争问题。
Happens-Before
原则包括:
Happens-Before
于后面的操做。unlock
操做 Happens-Before
于后面对同一个锁的 lock
操做。volatile
变量规则:对一个 volatile
变量的写操做 Happens-Before
于对该变量的读操做。Thread
对象的 start
方法 Happens-Before
于此线程的每个动做。Happens-Before
于对该线程的终止检测,可经过 Thread.join
方法结束,或 Thread.isAlive
方法的返回值,检测到线程已经终止执行。interrupt
方法的调用 Happens-Before
于被中断线程的代码检测到中断事件的发生。Happens-Before
于它的 finalize
方法的开始。A
Happens-Before
于操做 B
,操做 B
Happens-Before
于操做 C
,那么操做 A
就 Happens-Before
于操做 C
。