java是支持多线程的,可是其可见性,原子性,有序性是致使多线程bug的缘由,因此引入java内存模型来解决这些问题。html
java内存模型归纳来讲是解决可见性和有序性的。java
1)可见性 - 缓存致使缓存
当建立线程时JVM会为其建立本身的内存存储本身的私有变量,可是全部的共享变量都存在于主存(共享区域)中,全部线程的操做都须要在本身的私有内存中操做,多线程
因此当线程访问共享变量时须要先将主存中变量copy到本身的工做内存,操做完后,写会主存。 —— 故缓存会致使可见性bug并发
2)有序性 - 编译器优化app
当语句的执行顺序调整后 不会对结果形成影响时,编译器会进行优化,调整执行顺序。 —— 故会致使有序性bugide
例如:单例的double check问题,后续展开。优化
而java内存模型从某种角度来讲,提供了解决按需禁止缓存和编译优化的方法 。 包括 volatile,synchronized 和 final关键字,以及happens-before原则。spa
volatile禁用缓存,保证执行的有序性,但不能保证原子性。至关于弱化的synchronied。线程
可见性:
一、volatile修饰的共享变量,在工做内修改后,会强制立刻刷新到主存中。
二、valatile修饰的共享变量,一旦被一个线程修改完刷新到主存,则其余工做内存中的此变量都失效,再读取主存中的变量。
有序性:
volatile关键字修饰的变量以及以前的语句不会在JVM优化期间进行重排序(重排序:是指没有数据依赖的语句进行排序。在单线程内没有问题,优化效率还保证告终果的一致性,可是会影响并发中程序的正确性)
public class Singleton { /** * volatile修饰Singleton实例,保证singleton初始化时保证有序性 * instance = new Singleton(); * 其实JVM内部已经转换为多条指令: * //1:分配对象的内存空间 * memory = allocate(); * //2:初始化对象 * ctorInstance(memory); * //3:设置instance指向刚分配的内存地址 * instance = memory; * 可是通过重排序后以下: * //1:分配对象的内存空间 * memory = allocate(); * 3:设置instance指向刚分配的内存地址,此时对象还没被初始化 * instance = memory; * //2:初始化对象 * ctorInstance(memory); * * 假设没有volatile修饰。 * 线程1先占有锁,执行new Singleton(), 在给instance = memory分配内存地址的时候, * 线程2进入判断语句singleton==null,引用地址不为null, * 则线程2返回一个初始化不完整的实例,系统会报错 * --------------------- */ private static volatile Singleton singleton = null; private Singleton() { } public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) {// 2 singleton = new Singleton();// 3 } } } return singleton; } }
这涉及到关键的happens-before原则。happens-before是指前一个操做的结果对后续是可见的。
1)程序的顺序执行
2)volatile变量禁用缓存规则,对后续该变量的查看是可见的
3)传递性。A对于B可见,B对于C可见则A对于C可见
4)管程可见性。synchronized是管程的实现,前一个加锁的线程操做对后一个线程时可见的。 synchronied解锁后会强刷主存,因此后一个线程是可见的
5)线程start();这条是关于线程启动的。它是指主线程 A 启动子线程 B 后,子线程 B 可以看到主线程在启动子线程 B 前的操做.
final 关键字则是告诉编译器它是不变的,可劲优化
原文出处:https://www.cnblogs.com/volare/p/12227237.html