设计模式(一)单例模式

141 阅读3分钟

单例模式的写法

java

public final class SingleMode {
   private SingleMode() {
   }
   public final SingleMode getInstance() {
         return SingleMode.SingletonHolder.getSingleInstance();
      }
   private static final class SingletonHolder {
      @NotNull
      private static final SingleMode singleInstance = new SingleMode();
      
   }
}

kotlin

//饿汉模式
object SingleModel2 {
}

饿汉模式

饿汉模式是在类加载时就将单例实例构建出来

class SingleMode private constructor(){
    companion object{
        private val instance  = SingleMode()
        fun getInstance() : SingleMode{
            return instance
        }  
    }
}

这种模式的好处是,线程安全,只会有一个实例,没有任何锁,执行效率高

懒汉模式

class SingleMode private constructor(){
    companion object{
       
        @Volatile
        var instance : SingleMode? = null
        //普通懒汉,非线程安全
        fun getInstance1() : SingleMode{
            instance?.let {
                instance = SingleMode()
            }
            return instance!!
        }
        //双重校验,线程安全,多线程情况下会出现半对象的问题
        fun getInstanceDCL() : SingleMode{
            if(instance == null){
                synchronized(SingleMode::class.java){
                    if(instance == null){
                        instance = SingleMode()
                    }
                }
            }
            return instance!!
        }
        //静态内部类,线程安全
        fun getInstance2() : SingleMode{
            return SingletonHolder.singleInstance
        }
    }

    private class SingletonHolder{
        companion object{
             val singleInstance : SingleMode = SingleMode()
        }
    }

}

普通懒汉模式

普通的懒汉在对线程模式下可能会创建多个实例。试想一下有A,B两个线程同时调用getInstance1()方法,得到instance都是为空所以都会创建SingleMode实例。

双重校验模式

在双重校验下,线程是安全的,但是要注意instance需要用关键字Volatile修饰,防止产生半对象。双重校验模式第一重校验是为了防止对象已经被加载而导致的无意义的加锁。第二重校验则是为了防止产生多个实例对象,在创建实例对象时只允许有一个线程进入。

试想一下有A,B两个线程一前一后调用getInstanceDCL()方法,A线程先进入并且开始创建新的对象。由于instance = SingleMode()并不是一个原子操作,他会被分成三个步骤

  • 给SingleMode的实例分配内存
  • 调用SingleMode的构造函数,初始化成员字段
  • 将instance对象指向分配的空间(此时instance就不为null了)

如果我们没有加Volatile那么上面的步骤将是没有顺序的,可能是1,2,3,也可能是1,3,2。若在执行了1,3之后另外一个线程B调用了getInstanceDCL()方法那么此时instance对象已经不为空了,但是却没有被实例化,此时调用单例中的方法就会报错。

所以我们需要添加Volatile关键字,Volatile的作用是禁止指令重排,上述创建对象的顺序只能是1,2,3。 但是添加了Volatile会导致每次instance对象都从主内存中读取,影响性能。

静态内部类模式

当第一次加载SingleMode时不会初始化singleInstance,只有在第一次调用getInstance2()方法是才会导致singleInstance被初始化,因此对一次调用getInstance2()方法会导致虚拟机加载 SingletonHolder类,种种方式不经能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式。

kotlin中的单例模式

kotlin中通过object构造的单例经过编译后代码如下

public final class SingleModel2 {
   @NotNull
   public static final SingleModel2 INSTANCE;

   private SingleModel2() {
   }

   static {
      SingleModel2 var0 = new SingleModel2();
      INSTANCE = var0;
   }
}

可以看出kotlin中通过object得到的单例是懒汉模式