Kotlin 設計模式之單例模式

LarArthur 7年前發布 | 13K 次閱讀 單例模式 Kotlin

現在 Kotlin 的趨勢日益高漲,Jake Wharton 大神近期從 Square 公司離職到 Google 負責 Kotlin 部分。我最近分析了 Kotlin 下的單例模式的實現方式,與 Java 下的實現有點區別,之前寫過一篇 Java 設計模式之單例模式

餓漢式

Kotlin 引入了 object 類型,可以很容易聲明單例模式。

object Singleton {
    ...
}

// Kotlin 中調用
Singleton.xx()

// Java 中調用
Singleton.INSTANCE.xx()

這種方式和 Java 單例模式的餓漢式一樣,不過比 Java 中的實現代碼量少很多,其實是個語法糖。反編譯生成的 class 文件后如下:

public final class Singleton{
    public static final Singleton INSTANCE = null;

    static {
        Singleton singleton = new Singleton();
    }

    private Singleton(){
        INSTANCE = this;
    }
}

從反編譯的代碼可以看出 object 對象實際上還是利用了 INSTANCE 靜態變量,所以在 Java 中調用時需要使用 Singleton.INSTANCE.xx() 。

這種實現方式在類加載時就創建了單例對象,所以肯定是線程安全的,但是還是有餓漢式實現方式的問題:

  • 如果構造方法中有耗時操作的話,會導致這個類的加載比較慢。

  • 餓漢式一開始就創建實例,但是并沒有調用,會造成資源浪費。

  • 還有一個 Java 餓漢式單例模式沒有的問題:無法自定義構造函數,object 中不允許 constructor 函數。

懶漢式

前面的 object 的實現方式是餓漢式的,開始使用前就實例化好了,如何在第一次調用時在初始化呢?Kotlin 中的延遲屬性 Lazy 剛好適合這種場景。

class Singletonprivate constructor() {
    companion object {
        val instance: Singleton by lazy { Singleton() }
    }
}

// Kotlin 中調用
Singleton.instance.xx()

// Java 中調用
Singleton.Companion.getInstance().xx()

Lazy 延遲屬性默認是線程安全的,它具體是如何實現的呢?Java 中線程安全的懶漢式有 synchronized 修飾方法、雙重檢查鎖定、靜態內部類,更多內容請閱讀 Java 設計模式之單例模式 ,下面看 Lazy 屬性的源碼:

public fun <T>lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

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    // 聲明為 volatile 變量
    // 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
                    initializer = null
                    typedValue
                }
            }
        }

    ...
}

從上面代碼中可以看出延遲屬性 Lazy 內部也是使用雙重檢查鎖定來實現線程安全的延遲初始化的。

LazyThreadSafetyMode

延遲屬性 Lazy 默認線程安全模式是 LazyThreadSafetyMode.SYNCHRONIZED,使用同步鎖的,LazyThreadSafetyMode 共有三種模式:

  • SYNCHRONIZED – 使用同步鎖保證只有一個線程可以初始化實例。

  • PUBLICATION – 同一時期多個線程可以初始化實例,但是只有最先返回的值會作為延遲初始化的實例,使用 AtomicReferenceFieldUpdater.compareAndSet() 方法實現。

  • NONE – 沒有任何的線程安全的保證和開銷。

例如, lazy (LazyThreadSafetyMode.PUBLICATION, { LazySingleton() }) 。

小結

內存占用低時,可以選擇 object 聲明的餓漢式單例模式,簡單有效;如果初始化時需要額外的操作或者實例資源消耗大時,推薦 Lazy 延遲屬性的懶漢式單例模式。

 

來自:http://johnnyshieh.me/posts/kotlin-singleton-pattern/

 

 本文由用戶 LarArthur 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!