查漏补缺系列:写单例时须要注意这三点!

1、不简单的单例模式

//之前学习的low版单例
class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){}
    public static LazySingleton getInstance() {
        if (instance==null){
            instance=new LazySingleton();
        }
        return instance;
    }
}

写单例须要注意一下三点:
1)首先使用synchronized保证线程安全
2)使用double check机制来防止重复建立实例,就是先去判断实例是否存在,若是不存在就执行同步代码块,那这样的话,多个线程进来,会只有一个申请锁成功,其余阻塞。同步代码块里面还有实例空判断,这是为了一个线程建立实例成功后,其余线程进来就会判断已经有了实例了就不会再去new对象。
3)避免jvm指令重排是用了volatile修饰符。编译器(JIT),CPU 有可能对指令进行重排序,致使使用到还没有初始化的实例,能够经过添加volatile 关键字进行修饰,对于volatile 修饰的字段,能够防止指令重排。java

//这里使用的懒汉单例
class LazySingleton{
    private volatile static LazySingleton instance;
    //这里注意不要漏了重写构造空的私有构造函数,这里容易忘
    private LazySingleton(){}
    public static LazySingleton getInstance() {
        if (instance==null){
            synchronized (LazySingleton.class){
                if (instance==null){
                    instance=new LazySingleton();
                }
            }

        }
        return instance;
    }
}



简单扩展(加分项):

new Singleton()它并不是是一个原子操做,事实上在JVM大概作了如下3个事情:
  1. 给 singleton 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量,造成实例
  3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null了)程序员

  咱们都知道,在JVM的JIT即时编译器中存在指令重排序的优化。正常执行是1-2-3,但发生指令重排后有多是1-3-2。
  好比说有两个线程,线程一先执行了步骤3,在执行步骤2以前,此时线程二进来了,判断singleton实例时非null,注意此时singleton并未初始化,直接使用就报错了。安全

这里涉及了JVM的内存加载流程和指令重排的知识,有机会后边再讲下。jvm




hi~我是Mirror,一个为了自由安逸的将来而不断前进的的程序员。
若是你以为文章对你有一点点帮助,一个小小赞,即是对个人承认,若是有不足之处,也欢迎各位指正。函数

相关文章
相关标签/搜索