Kotlin设计模式(1)单例模式

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

  单例模式是开发中最常用的设计模式,Kotlin 中实现单例模式是很简单的,下面对比下 KotlinJava 单例模式实现。

一、饿汉模式

  • Java -饿汉模式
// 单例-饿汉模式
public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() { }
    public static Singleton get() { return instance; }
}
  • Kotlin -饿汉模式
// 单例-饿汉模式
object Singleton

  Kotlin 中实现 饿汉模式 的单例是非常简单的,通过关键字 object + 类名 即可实现。

  • Java -Kotlin 转换 Java

  为了验证 object + 类名 即可实现 kotlin 版本的单例模式,我们通过 Android studio 编译器将 Kotlin 代码转换为 Java 代码,如下:

public final class Singleton {
   public static final Singleton INSTANCE; 

   private Singleton() { }

   // 在类加载时执行
   static {
      Singleton var0 = new Singleton(); 
      INSTANCE = var0;
   }
}

  从转换后的代码中可以看出,定义的只读静态成员变量 INSTANCE 是在静态代码块中初始化,也就是说在类加载时已经对成员变量进行了初始化,同时构造方法为私有满足 饿汉模式 的单例特点,因此 object + 类名 实际是一个 饿汉模式 的单例。

二、懒汉模式

  • Java -懒汉模式
// 单例-懒汉模式
public class Singleton {
    private static volatile Singleton instance = null;

    private Singleton() { }

    public static Singleton get() {
        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
            }

        @JvmStatic
        fun get(): Singleton = instance!!
    }
}
  • Java -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();
   }

   @JvmStatic
   @NotNull
   public static final Singleton get() {  // ②
      return Companion.get();
   }

   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;
      }

      @JvmStatic
      @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();
      }
   }
}

  代码第②行构建了一个 Companion 对象的静态成员变量,在 Companion 对象中提供了 get() 方法获得 Singleton 实例,而在此 get() 方法中实际是调用代码⑤真正的创建 Singleton 实例,同时将此实例赋值给代码代码①的静态成员变量。从代码实现上看与我们上面的 Java 实现是一致的,所以上面的 kotlin 实现是 懒汉模式 的单例。

三、线程安全的懒汉模式

  • Java -线程安全的懒汉模式
// 单例-线程安全的懒汉模式
public class Singleton {

    private static volatile Singleton instance = null;

    private Singleton() { }

    public static Singleton get() {
        synchronized (Singleton.class) {
            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
            }

        @JvmStatic
        @Synchronized
        fun get(): Singleton = instance!!
    }
}
  • Java -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();
   }

   @JvmStatic
   @NotNull
   public static final synchronized Singleton get() {  // 1️⃣
      return Companion.get();
   }

   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;
      }

      @JvmStatic
      @NotNull
      public final synchronized Singleton get() {  // 2️⃣
         Singleton var10000 = ((Singleton.Companion)this).getInstance();
         if (var10000 == null) {
            Intrinsics.throwNpe();
         }

         return var10000;
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

  从代码可以看出,线程安全的懒汉模式转后的代码与懒汉模式转后的代码基本一致,只是比懒汉模式在代码1️⃣2️⃣中多出一个 synchronized 关键字修饰方法,保证方法在同一时间只有一个线程在访问。与我们实现 Java 版本的线程安全的懒汉模式是一样的,间接的验证了 Kotlin 代码实现的正确性。

四、双重校验锁的懒汉模式

  • Java -双重校验锁的懒汉模式
// 单例-双重校验锁的懒汉模式
public class Singleton {

    private static volatile Singleton instance = null;

    private Singleton() { }

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

}
  • Kotlin -双重校验锁的懒汉模式
// 单例-双重校验锁的懒汉模式
class Singleton private constructor() {
    companion object {
        val instance: Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { Singleton() }
    }
}

  kotlin 实现双重校验锁单例主要是通过关键字 lazy 实现的,所以此处应该看下 SYNCHRONIZED 模式下的 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)
    }

  SYNCHRONIZED 模式实际调用的是 SynchronizedLazyImpl(initializer),来看下SynchronizedLazyImpl 的实现:

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) {  // 1️⃣
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }

            return synchronized(lock) {  // 2️⃣
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {  // 3️⃣
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

  源码实现可以看出,SynchronizedLazyImpl 的核心代码是属性 valueget 访问器的实现。在 get 访问器中实现了双重校验锁的功能,代码第1️⃣3️⃣对应双重效验,代码第2️⃣进行加锁效验,防止 initializer 的多次调用。

五、静态内部类的懒汉模式

  • Java -静态内部类的懒汉模式
// 单例-静态内部类的懒汉模式
public class Singleton {

    private Singleton() { }

    public static Singleton get() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

}
  • Kotlin -静态内部类的懒汉模式
// 单例-静态内部类的懒汉模式
class Singleton private constructor() {

    companion object {
        @JvmStatic
        fun get(): Singleton = Holder.INSTANCE
    }

    private object Holder {
        val INSTANCE = Singleton()
    }

}
  • Java -Kotlin 转换 Java
public final class Singleton {
   public static final Singleton.Companion Companion = new Singleton.Companion((DefaultConstructorMarker)null);

   private Singleton() {
   }

   // $FF: synthetic method
   public Singleton(DefaultConstructorMarker $constructor_marker) {
      this();
   }

   @JvmStatic
   @NotNull
   public static final Singleton get() {
      return Companion.get();
   }

   private static final class Holder {
      @NotNull
      private static final Singleton INSTANCE;
      public static final Singleton.Holder INSTANCE;

      @NotNull
      public final Singleton getINSTANCE() {
         return INSTANCE;
      }

      static {
         Singleton.Holder var0 = new Singleton.Holder();
         INSTANCE = var0;
         INSTANCE = new Singleton((DefaultConstructorMarker)null);
      }
   }

   public static final class Companion {
      @JvmStatic
      @NotNull
      public final Singleton get() {
         return Singleton.Holder.INSTANCE.getINSTANCE();
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

  上述代码可以看出 Holder 对象是 Singleton 的静态内部类,同时在 Holder 的静态代码块中构建了 Singleton 的静态对象,因此只有在 Singleton 对象中提供一个能访问 Holder 对象中 INSTANCE 方法既可以实现静态内部类的单例,Holder 对象中提供了方法 getINSTANCE() 访问成员 INSTANCE,而 Companion 对象的 get 方法是调用 HoldergetINSTANCE() 方法返回 Singleton 对象,最后通过 Singleton 对象本身 get() 调用 Companion.get() 访问 Singleton 实例。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容