Kotlin——單例模式

最近在寫(xiě)項(xiàng)目的同時(shí)也用到了單例模式,kotlin的單例還不是很會(huì)寫(xiě),現(xiàn)在就總結(jié)下java寫(xiě)法對(duì)應(yīng)的kotlin是如何寫(xiě)的。

  • 餓漢式
  • 懶漢式
  • 線(xiàn)程安全的懶漢式
  • 雙重校驗(yàn)鎖式
  • 靜態(tài)內(nèi)部類(lèi)式

單例模式的基本思想就是在程序運(yùn)行過(guò)程中不會(huì)重復(fù)創(chuàng)建要使用的對(duì)象,有且只創(chuàng)建一次。這就需要用到kotlin中的object和companion object(伴生對(duì)象),因?yàn)樗麄兛梢猿洚?dāng)java下的static。

餓漢式實(shí)現(xiàn)

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

可以看到kotlin的餓漢單例實(shí)現(xiàn)只有一行代碼。用到了object關(guān)鍵字,object關(guān)鍵字定義一個(gè)類(lèi)并同時(shí)創(chuàng)建一個(gè)實(shí)例(就是一個(gè)對(duì)象)。它其中的一種使用場(chǎng)景就是對(duì)象聲明定義單例。

與類(lèi)一樣,一個(gè)對(duì)象聲明也可以包含屬性、方法、初始化語(yǔ)句塊等的聲明。唯一不允許的就是構(gòu)造方法(包括主構(gòu)造方法和從構(gòu)造方法)。與普通類(lèi)的實(shí)例不同,對(duì)象聲明在定義的時(shí)候就立即創(chuàng)建了,不需要在代碼的其他地方調(diào)用構(gòu)造方法。為對(duì)象聲明定義一個(gè)構(gòu)造方法是沒(méi)有意義的。

與變量一樣,對(duì)象聲明允許使用對(duì)象名.字符的方法來(lái)調(diào)用方法和訪(fǎng)問(wèn)屬性。

下面是kotlin餓漢式編譯成java代碼后的代碼,幫助我們理解。

public final class Singleton {
   public static final Singleton INSTANCE;

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

可以看到編譯后的Java代碼,直接將初始化對(duì)象的代碼放在了靜態(tài)代碼塊中。

餓漢式實(shí)現(xiàn)

  • 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!!//!!表示當(dāng)前對(duì)象不為空的情況下執(zhí)行
        }
    }
}

這里使用了伴生對(duì)象,在其內(nèi)部創(chuàng)建對(duì)象并調(diào)用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 {
    //創(chuàng)建單例對(duì)象
      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();
      }
   }
}

線(xiàn)程安全的懶漢式實(shí)現(xiàn)

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

雙重校驗(yàn)鎖式實(shí)現(xiàn)

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

雙重鎖模式應(yīng)該是日常開(kāi)發(fā)中比較常用的一種形式了,現(xiàn)在復(fù)習(xí)下其中的知識(shí)點(diǎn)。

  1. 第一次判斷singleton是否為null

第一次判斷是在synchronized同步代碼塊外進(jìn)行判斷,由于單例模式只會(huì)創(chuàng)建一個(gè)實(shí)例,并通過(guò)getInstance方法返回singleton對(duì)象,所以第一次判斷是為了在singleton對(duì)象已經(jīng)創(chuàng)建的情況下,避免進(jìn)入同步代碼塊,提升效率。

  1. 第二次判斷singleton是否為null

第二次是為了避免以下情況的發(fā)生。

(1)假設(shè):線(xiàn)程A已經(jīng)經(jīng)過(guò)第一次判斷,判斷singleton=null,準(zhǔn)備進(jìn)入同步代碼塊。

(2)此時(shí),線(xiàn)程B獲得時(shí)間片,由于線(xiàn)程A并沒(méi)有創(chuàng)建實(shí)例,所以判斷singleton仍為null,所以線(xiàn)程B創(chuàng)建了實(shí)例singleton。

(3)此時(shí),線(xiàn)程A再次獲得時(shí)間片,由于剛經(jīng)過(guò)第一次判斷singleton為null(不會(huì)重復(fù)判斷),進(jìn)入同步代碼快,這個(gè)時(shí)候如果不加入第二次判斷的話(huà),線(xiàn)程A又會(huì)創(chuàng)建一個(gè)實(shí)例singleton,就不滿(mǎn)足單例模式的需求,所以第二次判斷是很有必要的。

  1. 加volatile關(guān)鍵字的原因

第一,volatile可以保證可見(jiàn)性和原子性,同時(shí)保證JVM對(duì)指令不會(huì)進(jìn)行重排列。

第二,對(duì)象的創(chuàng)建不是一步完成的,是一個(gè)符合操作,需要三個(gè)指令。

singleton = new Singleton() 為例子

指令1:獲取singleton對(duì)象的內(nèi)存地址

指令2:初始化singleton對(duì)象

指令3:將這塊內(nèi)存地址指向引用變量singleton

由于volatile禁止JVM對(duì)指令進(jìn)行重排序,所以創(chuàng)建對(duì)象的過(guò)程仍然或按照指令1-2-3有序的執(zhí)行。

若沒(méi)有volatile關(guān)鍵字,在多線(xiàn)程的情況下,假設(shè)線(xiàn)程A正常創(chuàng)建一個(gè)實(shí)例,那么指定執(zhí)行的順序可能2-1-3,當(dāng)執(zhí)行到指令1的時(shí)候,線(xiàn)程B執(zhí)行g(shù)etInstance方法,獲取到的,可能是對(duì)象的一部分,或者是不正確的對(duì)象,程序可能就會(huì)報(bào)異常信息。

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

看到這種寫(xiě)法,哇,真的比java少了很多代碼,但是越簡(jiǎn)單的代碼就會(huì)包含越多的信息點(diǎn),我們需要理解為什么要這樣做。這里用到了by lazy(),kotlin的委托屬性的延遲屬性

延遲屬性:其值只在首次訪(fǎng)問(wèn)時(shí)計(jì)算。委托屬性具體可參考官方文檔

lazy函數(shù)返回一個(gè)對(duì)象,該對(duì)象具有一個(gè)名為getValue且簽名正確的方法,因此可以把它與by關(guān)鍵字一起使用來(lái)創(chuàng)建一個(gè)委托屬性。默認(rèn)情況下,lazy函數(shù)是線(xiàn)程安全的,如果需要可以設(shè)置其它選項(xiàng)來(lái)高速它要使用哪個(gè)鎖,或者完全避開(kāi)同步,如果該類(lèi)永遠(yuǎn)不會(huì)在多線(xiàn)程環(huán)境中使用。

下面來(lái)看看源碼加深理解:

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)
    }
    ...
}

進(jìn)入了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() {
            //判斷是否已經(jīng)初始化過(guò),如果初始化過(guò)直接返回,不在調(diào)用高級(jí)函數(shù)內(nèi)部邏輯
            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!!()//調(diào)用高級(jí)函數(shù)獲取其返回值
                    _value = typedValue//將返回值賦值給_value,用于下次判斷時(shí),直接返回高級(jí)函數(shù)的返回值
                    initializer = null
                    typedValue
                }
            }
        }
    //省略部分代碼
}

靜態(tài)內(nèi)部類(lèi)式實(shí)現(xiàn)

  • 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()
    }
}

注意

對(duì)象聲明(object)不是一個(gè)表達(dá)式,不能用在賦值語(yǔ)句的右邊。

對(duì)象聲明的初始化是線(xiàn)程安全的并且在首次訪(fǎng)問(wèn)時(shí)進(jìn)行。

對(duì)象聲明不能在局部作用域(即不能直接嵌套在函數(shù)內(nèi)部),但是他們可以嵌套到其他對(duì)象聲明或非內(nèi)部類(lèi)中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,241評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,939評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,697評(píng)論 6 409
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,182評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,406評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,772評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,973評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,638評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,866評(píng)論 1 285
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,644評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,953評(píng)論 2 373