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/