Java内存模型与单例模式

 2019-12-10 15:57  阅读(1064)
文章分类:Java Core

我们直接进入正题

这是双重校验锁的单例模式

private static Singleton instance;  //1

        public static Singleton getSingleton(){
            if (instance == null){   //2
                synchronized (Singleton.class){   //3
                    if (instance == null)   //4
                        instance = new Singleton();  //5   //实例化
                }
            }
            return instance;
        }

这段代码看起来两全其美

①多个线程试图在同一时间创建对象时,会通过加锁来保证只有一个线程能创建对象

②在对象创建完之后,执行getSingleton方法不需要获取锁,直接返回已创建好的对象

双重校验锁看起来似乎很完美,但这是一个错误的优化,在代码执行到第二行时,代码读取instance不为null,instance引用的对象有可能还没有完成初始化。

一、问题的根源

在创建一个对象时,可分这三个步骤

①分配对象的内存空间

②初始化对象

③将引用变量指向分配的内存地址(在这一步,引用变量就已经不是null了)

但是呢,上面步骤的2,3有可能被重排序,指向顺序如下

①分配对象的内存空间

②将引用变量指向分配的内存地址(在这一步,引用变量就已经不是null了)//此时,对象还没有初始化

②初始化对象

所以当第一个线程执行到第二步时,第二个线程第一次校验发现instance不是空,在使用时就出报错。

抠张图看看

20191210001439\_1.png

所以我们想办法

①不允许2、3步重排序

②运行2、3步重排序,但不允许其他线程看到这个重排序

解决方法一:基于vloatile的解决方案

我们只需要一点小小的修改,就可以实现线程安全的延迟初始化,那就将instance用volatile修饰,也就是下面的代码

private static volatile Singleton instance;

        public static Singleton getSingleton(){
            if (instance == null){
                synchronized (Singleton.class){
                    if (instance == null)
                        instance = new Singleton();
                }
            }
            return instance;
        }

当对象声明为volatile时,步骤2和步骤3之前的重排序,在多线程环境中将会被禁止。

解决方法二:基于类初始化的解决方案

JVM在类的初始化阶段,会执行类的初始化,在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。

基于这个特性,可以实现另一种线程安全的延迟初始化方案

private static class SingletonFactory{
            public static Singleton singleton = new Singleton();
        }

        private static Singleton getSingleton(){
            return SingletonFactory.singleton;
        }

抠张图看看这是为什么吧

20191210001439\_2.png

Java语言规范规定,对于每一个类或者接口,都有一个唯一的初始化锁预支对应。JVM在类初始化期间会获取这个锁,并且每隔线程至少获取一次锁来确保这个类已经被初始化过了。

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Java内存模型与单例模式

相关推荐