Java與Kotlin的單例模式(霸氣.jpg)

天星技術團隊發表於2018-07-25

作者: 點先生, 時間: 2018.7.25

Java與Kotlin的單例模式(霸氣.jpg)

題外話

上一次被人說文章名字取得不霸氣,於是這一次我採用了這麼霸氣的名字,但實際上我是一個很低調的人。設計模式剛入門的小夥伴可以先看看這篇《設計模式入門》,在文章末尾也將列出“設計模式系列”文章。歡迎大家關注留言投幣丟香蕉。

什麼是單例模式

  1. 單例模式是設計模式中最簡單的形式之一。
  2. 一個類有且僅有一個物件例項,並自行例項化向整個系統提供。

為何要學習單例模式

  1. 有些物件我們只需要一個。例如:執行緒池,快取,對話方塊等等,建立太多此類例項可能會導致程式行為異常、資源使用過量等問題。
  2. 防止造成資源浪費。靜態變數也可以給整個系統提供一個例項,但此物件在程式一開始就被建立好了,萬一在某次執行中沒有使用到此物件,資源就被浪費了。
  3. 沒有富蘿莉包養我
    Java與Kotlin的單例模式(霸氣.jpg)

走進單例模式

單例模式有三個要點,分別是:

  1. 僅有一個例項。
  2. 自動例項化。
  3. 向整個系統提供。

這也就是我們在寫單例模式時候必須要遵守的幾點。根據上面的條件,我們能夠得出:

  1. 構造方法必須私有化,禁止外部隨意new Singleton();
  2. 類內部例項化一個例項物件。
  3. 對外提供一個可以獲取內部唯一例項物件的方法。

懶漢式

懶漢式這名字的精髓在於懶,在程式碼中,這種懶代表著需要的時候才建立例項,不需要就不建立了,這樣保證了資源的不浪費。

Java懶漢式

public class JavaSingleton {

    private JavaSingleton(){}//私有構造方法
    private static JavaSingleton instance;//提供一個例項物件

     public static JavaSingleton getInstance(){//對外提供可獲取唯一例項化物件的方法
        if(instance == null)
            instance = new JavaSingleton(); //延遲例項化
        return instance;
    }
}
複製程式碼

Kotlin懶漢式1

class KotlinSingleton private constructor() {
    private var instance : KotlinSingleton? = null

    fun getInstance(): KotlinSingleton? {
        if(instance == null)
            instance = KotlinSingleton()
        return instance
    }
}
複製程式碼

Kotlin懶漢式2

class KotlinSingleton private constructor() {
    companion object {
        val instance by lazy(LazyThreadSafetyMode.NONE) {
            KotlinSingleton()
        }
    }
}

// 在Kotlin 中呼叫
KotlinSingleton.instance.xx()
// 在Java 中呼叫
KotlinSingleton.Companion.getInstance().xx()
複製程式碼

kotlin方式1就是直譯了java方式,沒啥說的。kotlin方式2寫著很簡潔,也很明瞭。要提一點的就是companion object,他類似public static,這一點從下面呼叫方式也看得出來。lazy屬性表明是懶載入方式,LazyThreadSafetyMode.NONE表明了這種方式,執行緒不安全。

為啥不安全??!!

我們已經按照單例模式三個要點寫出了單例模式,其實都已經講完了。但為何還有其他一些寫法呢? 因為我們是程式設計師啊!優化!優化!優化! 單執行緒程式下,懶漢式完全夠用。但實際開發哪還有什麼單執行緒程式。既然存在多執行緒,就存在併發性,如果兩個執行緒同時進入非空判斷,問題就出現了。執行緒一可能建立了一個instance,執行緒二因為已經非空判斷為空了,所以也建立了個instance。這樣程式肯定不會朝著你預想的方向去。所以我們可以給非空判斷加一把鎖,防止多執行緒同時進入非空判斷。

同步鎖

Java方式

public class JavaSingleton {

    private JavaSingleton(){}//私有構造方法
    private static JavaSingleton instance;//提供一個例項物件

     public static synchronized JavaSingleton getInstance(){//對外提供可獲取唯一例項化物件的方法
        if(instance == null)
            instance = new JavaSingleton(); //延遲例項化
        return instance;
    }
}
複製程式碼

Kotlin方式

class KotlinSingleton private constructor() {
    private var instance : KotlinSingleton? = null

    @Synchronized //用註解就okk了。
    fun getInstance(): KotlinSingleton? {
        if(instance == null)
            instance = KotlinSingleton()
        return instance
    }
}
複製程式碼

簡單!粗暴!太粗暴了!Synchronized 是一把重型鎖,對效能影響相當的大,像單例這樣在軟體中可能多次獲取,那效能將會大大地降低!所以,這種方式不建議使用。

餓漢式

為了解決多執行緒的問題,又不想降低效能,“餓漢式”就來了。餓漢式名字精髓在於“餓”,迫切的想吃東西,在程式碼中表示,迫切的想有一個單例物件!即在初始化的時候,就把單例建立好,之後要用就直接return,但是不用就浪費了。這也是餓漢式的缺點。

Java方式

public class JavaSingleton {

    private JavaSingleton(){}//私有構造方法
    private static JavaSingleton instance = new JavaSingleton();//提供一個例項物件

    public static JavaSingleton getInstance(){//對外提供可獲取唯一例項化物件的方法
        return instance;
    }
}
複製程式碼

Kotlin方式

object KotlinSingleton {
    //null
}

// 在Kotlin 中呼叫
KotlinSingleton.xx()
// 在Java 中呼叫
KotlinSingleton.INSTANCE.xx()

複製程式碼

酥糊!Kotlin!有牌面!

雙重檢查鎖

前面講的三種方式都有不同程度的缺點。而雙重檢查鎖,既保證了延遲載入不浪費資源,又保證了較好的效能。但雙重檢查鎖不適用於1.4以及更早版本的java。

Java方式

public class JavaSingleton {

    private JavaSingleton(){}
    //volatile確保了當instance初始化為JavaSingleton例項時,多個執行緒正確的處理instance變數。
    private volatile static JavaSingleton instance;

   public static JavaSingleton getInstance(){
       if(instance == null){
           synchronized (JavaSingleton.class){//只有第一次才徹底執行鎖塊程式碼
               if(instance == null){
                   instance = new JavaSingleton();
               }
           }
       }
       return instance;
   }
}
複製程式碼

Kotlin方式1

class KotlinSingleton private constructor() {
    @Volatile  //用註解就okk了
    private var instance : KotlinSingleton? = null

    fun getInstance(): KotlinSingleton? {
        if(instance == null){
            synchronized(KotlinSingleton::class){
                if(instance == null){
                    instance = KotlinSingleton()
                }
            }
        }
        return instance
    }
}
複製程式碼

Kotlin方式2

class KotlinSingleton private constructor() {
    companion object {
        val instance by lazy (mode = LazyThreadSafetyMode.SYNCHRONIZED){
            KotlinSingleton()
        }
    }
}

// 在Kotlin 中呼叫
KotlinSingleton.instance.xx()
// 在Java 中呼叫
KotlinSingleton.Companion.getInstance().xx()
複製程式碼

“雙重檢查鎖”的沒有明顯的缺點,如果非要說一個,可能就是太複雜了。kotlin還好,java裡面寫著相當的複雜。如果程式對效能沒有考慮的話,這樣寫顯然就太麻煩了。

關於LazyThreadSafetyMode
延遲屬性 Lazy 可選LazyThreadSafetyMode三種模式:

  • SYNCHRONIZED —— 雙重檢查鎖式,預設使用。
  • PUBLICATION —— 允許多個執行緒同時初始化例項,但只採用最先返回的例項。
  • NONE —— 沒有任何的執行緒安全的保證和開銷。

內部類式(店長推薦!!)

不要私信問我店長是誰! 總之這種方式,解決了延遲載入,執行緒安全的問題,還程式碼量少!簡直美滋滋!但跟之前不同的是,沒有宣告例項物件。

Java方式

public class JavaSingleton {

    private JavaSingleton(){}

    private static class Holder{
        private static JavaSingleton instance = new JavaSingleton();
    }

    public static JavaSingleton getInstance(){
        return Holder.instance;
    }
}
複製程式碼

Kotlin方式

class KotlinSingleton private constructor() {
    companion object {
        fun getInstance() = Holder.instance
    }

    private object Holder{
        val instance = KotlinSingleton()
    }
}
複製程式碼

在類載入時,因為沒有呼叫getInstance()所以Holder也不會載入,這樣就實現了懶載入。呼叫getInstance()時,JVM會主動保證類載入的安全性,所以執行緒也是安全的。kotlin的寫法就是java的翻譯版本。

民間大神版本

上面幾種方式都是比較官方的版本,下面介紹幾個民間版本,是真大神還是抖機靈,自行判斷。我也去研究研究!

Java與Kotlin的單例模式(霸氣.jpg)

來自知乎@丌冰: “我覺得這樣就好了,什麼懶載入,雙重什麼什麼,什麼什麼的”

fun main(args: Array<String>) {
    Instance.INSTANCE.fun1()
    print(Instance.INSTANCE.fun2())
}
enum class Instance {
    INSTANCE;

    fun fun1(){
    }
    fun fun2():Any?{
        return null
    }
}

作者:丌冰
連結:https://www.zhihu.com/question/52377186/answer/303561470
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
複製程式碼

Java與Kotlin的單例模式(霸氣.jpg)
可見Android官方並不推薦使用列舉,佔用記憶體較多。

來自劉望舒設計模式(二)單例模式的七種寫法

public class SingletonManager { 
    private static Map<String, Object> objMap = new HashMap<String,Object>();
    private Singleton() { 
    }
    public static void registerService(String key, Objectinstance) {
        if (!objMap.containsKey(key) ) {
            objMap.put(key, instance) ;
        }
    }
    public static ObjectgetService(String key) {
        return objMap.get(key) ;
    }
}
複製程式碼

這也是比較少見的單例寫法,將多個單例放在進SingletonManager 的靜態map中統一管理。我是覺得有點太過於複雜,容易出錯。

以上版本僅供參考,要是用了對程式造成了什麼不好的影響,別找我。

關於單例的其他問題

關於繼承: 不能繼承!別想了。
關於單例: 儘量少使用單例。
關於我還沒想到的問題: 歡迎加群討論557247785。

總結

單例方式 優點 缺點
懶漢式 懶載入 執行緒不安全
同步鎖 執行緒安全 多次獲取的效能很低
餓漢式 簡單、易寫 可能引起資源浪費
雙重檢查鎖 第一次獲取才加鎖 寫法複雜
內部類式 簡單、易寫 暫無
民間大神式 歡迎留言跟我討論 或者加qq群:557247785

以下是我“設計模式系列”文章,歡迎大家關注留言投幣丟香蕉。

設計模式入門
Java與Kotlin的單例模式
Kotlin的裝飾者模式與原始碼擴充套件
由淺到深瞭解工廠模式

相關文章