亲宝软件园·资讯

展开

Java 内存模型

以梦为码 人气:2

 

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优化期间进行重排序(重排序:是指没有数据依赖的语句进行排序。在单线程内没有问题,优化效率还保证了结果的一致性,但是会影响并发中程序的正确性)

 

   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;
    }
}
单例double check问题

 

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 关键字则是告诉编译器它是不变的,可劲优化

 

 

加载全部内容

相关教程
猜你喜欢
用户评论