擁抱kotlin:利用kotlin實現單例模式

java的單例模式幾種寫法都已經很熟悉了,但轉到kt時若是用java寫法實現倒顯得怪異了,有的能夠藉助kt的約定輕鬆完成。java

1、幾種常見的單例模式

  1. 餓漢式(一來就建立,無論是否真的須要)
  2. 懶漢式(延遲加載,使用時才建立,這裏就直接討論double check)
  3. 靜態內部類(藉助虛擬機實現同步、內部類機制實現延遲初始化)

2、kotlin這幾種方式的寫法

1.餓漢式安全

這種方式無需使用java的private constroctor,getInstance這些接口,直接使用object就可實現。bash

object HungrySin {
    fun calculate() {

    }
}
複製代碼

反編譯成java代碼,就是熟悉的java寫法ide

public final class HungrySin {
   public static final HungrySin INSTANCE;

   public final void calculate() {
   }

   private HungrySin() {
   }

   static {
      HungrySin var0 = new HungrySin();
      INSTANCE = var0;
   }
}
複製代碼

2.懶漢式 doubleCheck函數

說到延遲加載就直接跳到線程安全且性能較好的doubleCheck吧,如果用java實現會用2層check,第一層判斷減輕鎖的負擔直接判斷是否建立過,第二層判斷加鎖保證線程安全,最後用volatile禁止重排序防止編譯器優化致使的線程安全問題。性能

在kotlin裏面也無需這麼複雜,直接使用by lazy代理便可實現優化

class DoubleCheckSin private constructor() {
    companion object {
        val doubleCheckSin: DoubleCheckSin by lazy {
            DoubleCheckSin()
        }
    }

    fun calculate() {

    }
}
複製代碼

這個要歸功於委託和lazy.ktui

@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

複製代碼

能夠看到上文,把val doubleCheckSin委託給了lazy, get時返回的是lazy.kt的valuethis

重點來看看lazyspa

layz支持傳LazyThreadSafetyMode,有三種可選,默認的是SYNCHRONIZED,也就是線程安全,下面分析一下線程安全的具體實現。

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    //0._value用volatile禁止指令重排序
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    //1.這個是上面分析的單例委託的value
    override val value: T
        get() {
            val _v1 = _value
            //2.若已經初始化,則直接返回  同java中的第一層判斷
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            
            return synchronized(lock) {
                val _v2 = _value
                //3.第二層判斷,加synchronized關鍵幀 保證線程安全
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                   //4.第一次建立,把執行lazy下傳進來的lambda 單例這裏也就是值的構造構造函數
                    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)
}
複製代碼

上面的代碼對應的地方加了註釋

  1. _value用volatile禁止指令重排序
  2. 這個是上面分析的單例委託的value
  3. 若已經初始化,則直接返回 同java中的第一層判斷
  4. 第二層判斷,加synchronized關鍵幀 保證線程安全
  5. 第一次建立,把執行lazy下傳進來的lambda 單例這裏也就是值的構造構造函數

由此能夠看出lazy的SYNCHRONIZED模式實現和java是徹底同樣的(除了委託和lambda函數),可是使用koltin by lazy實現doubelCheck單例就就能夠少寫不少代碼

對於by layz其實用的更多的地方是view的延遲加載,道理和單例同樣的。第一次訪問的時候才執行lambda來初始化,後面就直接取以前建立的對象。

3.靜態內部類

這個其實沒有取巧的地方了,仍是得像java同樣要聲明一個靜態內部類,經過虛擬機的靜態內部類加載機制實現線程安全、延遲加載。

class InnerSin private constructor() {
    companion object {
        val instance = Holder.holder
    }


    private object Holder {
        val holder = InnerSin()
    }


    fun calculate() {

    }
}
複製代碼

不過這種方式很巧妙,利用加載類的cinit方法特行,保證加載靜態類holder時建立InnerSin對象是線程安全且只會有一個實例。而後加載外部類加載不會立刻加載內部類,等到外部類調用內部類的時候才加載,保證了延遲加載。

相關文章
相關標籤/搜索