Java 内存模型
1、为什么要引入java内存模型
java是支持多线程的,但是其可见性,原子性,有序性是导致多线程bug的原因,所以引入java内存模型来解决这些问题。
2、什么是java内存模型
java内存模型概括来说是解决可见性和有序性的。
1)可见性 - 缓存导致
当创建线程时JVM会为其创建自己的内存存储自己的私有变量,但是所有的共享变量都存在于主存(共享区域)中,所有线程的操作都需要在自己的私有内存中操作,
所以当线程访问共享变量时需要先将主存中变量copy到自己的工作内存,操作完后,写会主存。 —— 故缓存会导致可见性bug
2)有序性 - 编译器优化
当语句的执行顺序调整后 不会对结果造成影响时,编译器会进行优化,调整执行顺序。 —— 故会导致有序性bug
例如:单例的double check问题,后续展开。
而java内存模型从某种角度来说,提供了解决按需禁止缓存和编译优化的方法 。 包括 volatile,synchronized 和 final关键字,以及happens-before原则。
3、volatile关键字
volatile禁用缓存,保证执行的有序性,但不能保证原子性。相当于弱化的synchronied。
可见性:
1、volatile修饰的共享变量,在工作内修改后,会强制马上刷新到主存中。
2、valatile修饰的共享变量,一旦被一个线程修改完刷新到主存,则其他工作内存中的此变量都失效,再读取主存中的变量。
有序性:
volatile关键字修饰的变量以及之前的语句不会在JVM优化期间进行重排序(重排序:是指没有数据依赖的语句进行排序。在单线程内没有问题,优化效率还保证了结果的一致性,但是会影响并发中程序的正确性)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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; } }
4、volatile如何禁用缓存
这涉及到关键的happens-before原则。happens-before是指前一个操作的结果对后续是可见的。
1)程序的顺序执行
2)volatile变量禁用缓存规则,对后续该变量的查看是可见的
3)传递性。A对于B可见,B对于C可见则A对于C可见
4)管程可见性。synchronized是管程的实现,前一个加锁的线程操作对后一个线程时可见的。 synchronied解锁后会强刷主存,所以后一个线程是可见的
5)线程start();这条是关于线程启动的。它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作.
5、final关键字
final 关键字则是告诉编译器它是不变的,可劲优化