• DCL单例模式中的缺陷及单例模式的其他实现


      DCL:Double Check Lock ,意为双重检查锁。在单例模式中懒汉式中可以使用DCL来保证程序执行的效率。

     1 public class SingletonDemo {
     2     private static SingletonDemo singletonDemo = null;
     3     private SingletonDemo(){
     4     }
     5 
     6     public SingletonDemo getSingletonDemo(){
     7         if(singletonDemo == null){
     8             synchronized (SingletonDemo.class){
     9                 if(singletonDemo == null){
    10                     singletonDemo = new SingletonDemo();
    11                 }
    12             }
    13         }
    14         return singletonDemo;
    15     }
    16 }

    上面是传统的DCL单例模式一种实现,第一个空值判断是为了避免实例属性已经实例化赋值后,后面的线程依然进入 synchronized 修饰的代码块,进行加锁、解锁,造成效率低下;第二个空值判断是为了避免实例属性已经赋值后,等待队列中的线程重复执行对象创建与赋值。而DCL可以保证多线程下只会进行一次对象初始化。但是这样的代码还是会有缺陷。

    缺陷

    指令集

      让我们单独写一个对象创建方法,看一下这个操作对应的指令集是什么

    public void aa(){
            singletonDemo = new SingletonDemo();
        }

       先对这个类进行编译,然后使用idea 的 jclasslib插件对这个类进行查看,指令集如下:

      

     指令分别是 new 、dup、invokespecial、putstatic,最后return返回,这里dup是复制操作,也就是对栈顶的元素复制一份,这里可以忽略不看。所以关键的指令是三个,1、new(实例化,对对象进行声明,分配地址),2、invokespecial(初始化,调用构造方法,进行属性赋值),3、putstatic(将这个对象赋值给属性singletonDemo)。

    指令重排

      单线程下存在着指令重排,什么是指令重排,比如说代码 x=1; y=1; x++; y++; y=x+y; JVM在加载时可能先加载到 y,那么它不会再去等待x加载,直接去执行 y++  ,这样就提高了运算效率,这种代码冲排序就是指令重排。但同时指令重排也不是随意的重排,它会遵守数据依赖性,比如虽然先加载了y,执行了 y++ ,也加载了 x,但是并不会接着去执行 y=x+y;因为右边操作的y 在前面的 y++修改了值,所以产生了对y++数据依赖,JVM并不会允许这样的指令重排(其实这个例子里的x++,y++指令会划分为三步,这里只需要知道表达的意思就可以了)。但是在多线程下数据依赖性就不能保证线程安全问题了。

      回到前面的对象创建的指令集,2和3因为不存在数据依赖所以可能发生指令重排(3只是将对象地址赋值给变量,所以并不会与2存在数据依赖),所以在多线程下,可能线程1在执行new指令后直接执行指令3,线程2就执行到第7行第一个非空判断了,此时因为对象地址分配了,所以判断是非空,直接return,但是此时还没有执行初始化指令,所以该对象只是分配了空间还没有创建完对象,导致这个方法还是返回了一个null值(这个null值表示的是该位置的对象实际是获取不到的但是判断是非空的)。

    解决

      volatile 关键字可以禁止指令重排和保证可见性,但是由于不能保证原子性,所以在这里还是需要配合 synchronized 来使用。关于volatile在多线程基础里面说到了,所以这里最终的代码是:

     1 public class SingletonDemo {
     2     private volatile static SingletonDemo singletonDemo = null;
     3     private SingletonDemo(){
     4     }
     5 
     6     public SingletonDemo getSingletonDemo(){
     7         if(singletonDemo == null){
     8             synchronized (SingletonDemo.class){
     9                 if(singletonDemo == null){
    10                     singletonDemo = new SingletonDemo();
    11                 }
    12             }
    13         }
    14         return singletonDemo;
    15     }
    16 }

      

    补充

      单例模式的其他实现:

    饿汉式

      由于饿汉式是类加载时就会将对象实例创建赋值完成,所以在多线程下也是安全的,所以它的优点是不存在线程安全问题,缺点是没有延迟加载的优势,比如这个单例模式对象是一开始就加载好的,但是整个程序执行过程中过了很久才用上,那么从类被加载时就创建在堆中,一直到被用上,在堆中是一直占用空间的,如果存在多个饿汉式的单例类,就无形提高了GC发生的次数。降低程序的性能。

    1、直接实例化饿汉式

    public class Singleton1 {
        private static final Singleton1 INSTANCE=new Singleton1();
        
        private Singleton1() {
        }
        public static Singleton1 getSingleton() {
            return INSTANCE;
        }
    }

    特点:简单直接   

    2、枚举式饿汉式

    public enum Singleton12 {
        INSTANCE;
        public void aa() {            //要调用的方法
        }
    }

    特点:最简洁

    3、静态代码块饿汉式

    public class Singleton13 {
        private static final Singleton13 INSTANCE;
        static {
            INSTANCE=new Singleton13();
        }
        public static Singleton13 getSingleton() {
            return INSTANCE;
        }
    }

    特点:可以在类初始化时增加其他操作

    懒汉式

      懒汉式单例模式就是直到调用方法去获取对象时才会创建对象,会有线程安全问题,所以更为复杂,但是因为是延迟加载,所以会有延迟加载的优势。

    1、DCL懒汉式

      代码如上。

    2、静态内部类懒汉式

    public class Singleton22 {
        private Singleton22() {
            
        }
        
        private static class Inner{
            private static final Singleton22 INSTANCE=new Singleton22();
        }
        
        public static Singleton22 getInstance() {
            return Inner.INSTANCE;
        }
    }

    相比于DCL懒汉式更加简单,同时也没有加锁解锁操作,更加高效。

  • 相关阅读:
    spring下配置shiro
    web.xml文件配置说明
    spring中配置缓存—ehcache
    applicationContext.xml配置简介
    spring task定时器的配置使用
    spring配置数据库连接池druid
    Mybatis使用pageHelper步骤
    mybatis-generator和TKmybatis的结合使用
    PHP删除一个目录下的所有文件,不删除文件夹
    nodejs学习
  • 原文地址:https://www.cnblogs.com/mengxinJ/p/13948537.html
Copyright © 2020-2023  润新知