美文网首页面试基础
Kotlin——单例模式

Kotlin——单例模式

作者: 李die喋 | 来源:发表于2020-06-21 00:46 被阅读0次

    最近在写项目的同时也用到了单例模式,kotlin的单例还不是很会写,现在就总结下java写法对应的kotlin是如何写的。

    • 饿汉式
    • 懒汉式
    • 线程安全的懒汉式
    • 双重校验锁式
    • 静态内部类式

    单例模式的基本思想就是在程序运行过程中不会重复创建要使用的对象,有且只创建一次。这就需要用到kotlin中的object和companion object(伴生对象),因为他们可以充当java下的static。

    饿汉式实现

    • java
    public class Singleton{
        private static Singleton instance = new Singleton();
        
        private Singleton(){}
        
        public static Singleton getInstance(){
            return instance;
        }
    }
    
    • kotlin
    object Singleton
    

    可以看到kotlin的饿汉单例实现只有一行代码。用到了object关键字,object关键字定义一个类并同时创建一个实例(就是一个对象)。它其中的一种使用场景就是对象声明定义单例。

    与类一样,一个对象声明也可以包含属性、方法、初始化语句块等的声明。唯一不允许的就是构造方法(包括主构造方法和从构造方法)。与普通类的实例不同,对象声明在定义的时候就立即创建了,不需要在代码的其他地方调用构造方法。为对象声明定义一个构造方法是没有意义的。

    与变量一样,对象声明允许使用对象名.字符的方法来调用方法和访问属性。

    下面是kotlin饿汉式编译成java代码后的代码,帮助我们理解。

    public final class Singleton {
       public static final Singleton INSTANCE;
    
       static {
          Singleton var0 = new Singleton();
          INSTANCE = var0;
       }
    }
    

    可以看到编译后的Java代码,直接将初始化对象的代码放在了静态代码块中。

    饿汉式实现

    • java
    public class Singleton{
        private static Singleton instance;
        private Singleton(){}
        public static Singleton getInstance(){
            if(instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    • kotlin
    class Singleton private constructor(){
        companion object{
            private var instance:Singleton? = null
                get(){
                    if(field == null){
                        field = Singleton()
                    }
                    return field
                }
            fun get():Singleton{
                return instance!!//!!表示当前对象不为空的情况下执行
            }
        }
    }
    

    这里使用了伴生对象,在其内部创建对象并调用get()方法返回。

    看看kotlin代码编译成java代码的样子:

    public final class Singleton {
       private static Singleton instance;
       public static final Singleton.Companion Companion = new Singleton.Companion((DefaultConstructorMarker)null);
    
       private Singleton() {
       }
    
       // $FF: synthetic method
       public Singleton(DefaultConstructorMarker $constructor_marker) {
          this();
       }
    
       public static final class Companion {
        //创建单例对象
          private final Singleton getInstance() {
             if (Singleton.instance == null) {
                Singleton.instance = new Singleton((DefaultConstructorMarker)null);
             }
    
             return Singleton.instance;
          }
    
          private final void setInstance(Singleton var1) {
             Singleton.instance = var1;
          }
    
          @NotNull
          public final Singleton get() {
             Singleton var10000 = ((Singleton.Companion)this).getInstance();
             if (var10000 == null) {
                Intrinsics.throwNpe();
             }
    
             return var10000;
          }
    
          private Companion() {
          }
    
          // $FF: synthetic method
          public Companion(DefaultConstructorMarker $constructor_marker) {
             this();
          }
       }
    }
    

    线程安全的懒汉式实现

    • java
    public class Singleton{
        private static Singleton instance;
        private Singleton(){}
        public static synchronized Singleton getInstance(){//使用同步锁
            if(instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    • kotlin
    class Singleton private constructor(){
        companion object{
            private var instance: Singleton? = null
                get(){
                    if(field == null){
                        field = Singleton()
                    }
                    return field
                }
            @Synchronized
            fun get(): Singleton{
                return instance!!
            }
        }
    }
    

    在kotlin中如果需要将方法声明为同步,需要添加 @Synchronized注解。

    康康编译为java代码的样子:

    public final class Singleton {
       private static Singleton instance;
       public static final Singleton.Companion Companion = new Singleton.Companion((DefaultConstructorMarker)null);
    
       private Singleton() {
       }
    
       // $FF: synthetic method
       public Singleton(DefaultConstructorMarker $constructor_marker) {
          this();
       }
    
       public static final class Companion {
          private final Singleton getInstance() {
             if (Singleton.instance == null) {
                Singleton.instance = new Singleton((DefaultConstructorMarker)null);
             }
    
             return Singleton.instance;
          }
    
          private final void setInstance(Singleton var1) {
             Singleton.instance = var1;
          }
        
        //get()方法 使用了同步锁
          @NotNull
          public final synchronized Singleton get() {
             Singleton var10000 = ((Singleton.Companion)this).getInstance();
             if (var10000 == null) {
                Intrinsics.throwNpe();
             }
    
             return var10000;
          }
    
          private Companion() {
          }
    
          // $FF: synthetic method
          public Companion(DefaultConstructorMarker $constructor_marker) {
             this();
          }
       }
    }
    

    双重校验锁式实现

    • java
    public class Singleton{
        private volatile static Singleton instance;
        private Singleton(){}
        public static Singleton getInstance(){
            if(instance == null){
                synchronized(Singleton.class){
                    if(instance == null){
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    双重锁模式应该是日常开发中比较常用的一种形式了,现在复习下其中的知识点。

    1. 第一次判断singleton是否为null

    第一次判断是在synchronized同步代码块外进行判断,由于单例模式只会创建一个实例,并通过getInstance方法返回singleton对象,所以第一次判断是为了在singleton对象已经创建的情况下,避免进入同步代码块,提升效率。

    1. 第二次判断singleton是否为null

    第二次是为了避免以下情况的发生。

    (1)假设:线程A已经经过第一次判断,判断singleton=null,准备进入同步代码块。

    (2)此时,线程B获得时间片,由于线程A并没有创建实例,所以判断singleton仍为null,所以线程B创建了实例singleton。

    (3)此时,线程A再次获得时间片,由于刚经过第一次判断singleton为null(不会重复判断),进入同步代码快,这个时候如果不加入第二次判断的话,线程A又会创建一个实例singleton,就不满足单例模式的需求,所以第二次判断是很有必要的。

    1. 加volatile关键字的原因

    第一,volatile可以保证可见性和原子性,同时保证JVM对指令不会进行重排列。

    第二,对象的创建不是一步完成的,是一个符合操作,需要三个指令。

    singleton = new Singleton() 为例子
    

    指令1:获取singleton对象的内存地址

    指令2:初始化singleton对象

    指令3:将这块内存地址指向引用变量singleton

    由于volatile禁止JVM对指令进行重排序,所以创建对象的过程仍然或按照指令1-2-3有序的执行。

    若没有volatile关键字,在多线程的情况下,假设线程A正常创建一个实例,那么指定执行的顺序可能2-1-3,当执行到指令1的时候,线程B执行getInstance方法,获取到的,可能是对象的一部分,或者是不正确的对象,程序可能就会报异常信息。

    • kotlin
    class SingletonDemo private constructor() {
        companion object {
            val instance: SingletonDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            SingletonDemo() }
        }
    }
    

    看到这种写法,哇,真的比java少了很多代码,但是越简单的代码就会包含越多的信息点,我们需要理解为什么要这样做。这里用到了by lazy(),kotlin的委托属性的延迟属性

    延迟属性:其值只在首次访问时计算。委托属性具体可参考官方文档

    lazy函数返回一个对象,该对象具有一个名为getValue且签名正确的方法,因此可以把它与by关键字一起使用来创建一个委托属性。默认情况下,lazy函数是线程安全的,如果需要可以设置其它选项来高速它要使用哪个锁,或者完全避开同步,如果该类永远不会在多线程环境中使用。

    下面来看看源码加深理解:

    public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
        //三种模式
        when (mode) {
            LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
            LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
            LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
        }
        ...
    }
    

    进入了SynchronizedLazyImpl():

    internal object UNINITIALIZED_VALUE
    
    private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
        private var initializer: (() -> T)? = initializer
        @Volatile private var _value: Any? = UNINITIALIZED_VALUE
        // final field is required to enable safe publication of constructed instance
        private val lock = lock ?: this
    
        override val value: T
            get() {
                //判断是否已经初始化过,如果初始化过直接返回,不在调用高级函数内部逻辑
                val _v1 = _value
                if (_v1 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST")
                    return _v1 as T
                }
    
                return synchronized(lock) {
                    val _v2 = _value
                    if (_v2 !== UNINITIALIZED_VALUE) {
                        @Suppress("UNCHECKED_CAST") (_v2 as T)
                    } else {
                        val typedValue = initializer!!()//调用高级函数获取其返回值
                        _value = typedValue//将返回值赋值给_value,用于下次判断时,直接返回高级函数的返回值
                        initializer = null
                        typedValue
                    }
                }
            }
        //省略部分代码
    }
    

    静态内部类式实现

    • java
    public class Singleton{
        private static class SingletonHolder{
            private static Singleton instance = new Singleton();
        }
        
        public static Singleton getInstance(){
            return SingletonHolder.instance;
        }
    }
    
    • kotlin
    class Singleton private constructor(){
        companion object{
            val instance = SingletonHolder.holder
        }
        
        private object SingletonHolder{
            val holder = Singleton()
        }
    }
    

    注意

    对象声明(object)不是一个表达式,不能用在赋值语句的右边。

    对象声明的初始化是线程安全的并且在首次访问时进行。

    对象声明不能在局部作用域(即不能直接嵌套在函数内部),但是他们可以嵌套到其他对象声明或非内部类中。

    相关文章

      网友评论

        本文标题:Kotlin——单例模式

        本文链接:https://www.haomeiwen.com/subject/djgnxktx.html