天天看點

單例模式在伺服器報錯_單例模式-您可能做錯了什麼!

單例模式在伺服器報錯

單例模式在伺服器報錯_單例模式-您可能做錯了什麼!

單例模式絕對是所有設計模式中最簡單的模式,但是請相信我,我們許多人做錯了。 是以,讓我們進一步了解這種模式。

我們都知道使用Singleton模式的動機。 這種模式允許我們在應用程式的整個生命周期中隻有一個對象執行個體。 為什麼這很重要? 因為首先,當您隻想要一個對象時,您不想建立多個資源消耗大的對象。 其次,有時,建立多個執行個體不僅成本高昂,而且還會使您進入不一緻狀态。 例如,您不希望有多個資料庫對象,因為一個對象的更改可能會使其他對象不一緻。

但是現在您可能在想,如果我隻需要一個對象并且想從任何地方通路它,為什麼不能将其設定為全局變量? 答案是,是的,您可以将其設為全局變量,但是如果該對象建立需要大量資源使用該怎麼辦? 全局變量可能僅在應用程式啟動時建立。 您不希望Android應用程式有太多啟動時間,對嗎? 這些小東西在移動世界中很容易注意到。 為了防止這種情況,我們使用延遲加載,這意味着僅在需要時才建立對象。 一會兒我們将看到。

既然我們已經清楚了為什麼要使用單例對象,讓我們看看單例對象的特征是什麼。

  1. 單例類不應允許其他類執行個體化它。 這意味着,沒有一個類應該完全可以做

    new Singleton()

    。 我們如何防止這種情況? 這很簡單。 隻需将singleton類的構造函數設為

    private

    。 這個簡單的步驟確定了不會在任何地方執行個體化該對象……除了在同一類中之外(可以從同一類中調用私有構造函數-私有方法和字段也是如此)。 以下是Kotlin中帶有私有構造函數的類的簡單示例。
class  Singleton private constructor ()      

2.該類應該可以通路Singleton類:即使我們限制了Singleton類的執行個體化,我們仍然可以通路這種對象。 這是通過建立一個靜态方法來傳回Singleton對象。 在下面的示例中,我們制作了一個靜态方法來檢查執行個體是否為null。 如果是這樣,它将執行個體化一個新對象并将其配置設定給

INSTANCE

變量,該變量将在随後對

getInstance

方法的調用中傳回。

class  Singleton private constructor (){

    companion object  {
        private var INSTANCE : Singleton ? = null
        fun  getInstance(): Singleton{
            if ( INSTANCE  == null ){
                INSTANCE  = Singleton()
            }
            return INSTANCE !!
        }
    }

}      

實作單例模式的大多數人都正确地做到了以上兩點。 但是上述代碼的問題在于,它可能會在多線程環境中産生您的Singleton類的多個執行個體。

fun  getInstance(): Singleton{
        if ( INSTANCE  == null ){
                                   // <--- Imagine Thread 2 is here      
INSTANCE  = Singleton() // <--- Imagine Thread 1 is here      
}
...      

在上面的代碼中, 線程1和線程2都将産生兩個不同的對象,這違反了單例類的目的。 這可能會對您的應用程式造成災難性的影響,并且可能很難調試,因為與其他多線程問題一樣,它們僅在某些情況下才會發生。

是以,簡單而輕松的解決方案是僅在

synchronized

鎖内執行代碼。 就像這樣:

fun   getInstance(): Singleton {
     synchronized ( this ) {
         if ( INSTANCE  == null ){
             INSTANCE  = Singleton()
         }
         return INSTANCE !!
     }             
 }      

上面的代碼将解決建立多個執行個體的情況,但可能會導緻性能問題。 這意味着,即使在正确執行個體化了一個單例對象之後,對它的後續通路也将被同步,這是不必要的。 在多線程環境中讀取對象沒有問題。 一個好的停放名額是考慮同步代碼塊下的任何塊都會使該塊的執行速度降低100倍 。 如果您對此費用表示“滿意”,而又不需要經常通路該單例對象,則可以在此處停止。 但是,如果确實需要多次通路它,則對它進行優化會有所幫助。

要對其進行優化,請考慮我們實際需要優化的内容。 我們隻需要優化對象建立流程,而不需要優化對象流程的讀取。

建立

INSTANCE

變量後,同步流完全是不需要的開銷。

一個簡單的解決方法是比懶惰地更積極地建立對象。 看起來像這樣:

class  Singleton private constructor (){
    companion object  {
        val INSTANCE  = Singleton()
         fun   getInstance(): Singleton {
             return INSTANCE             
         }
    }
}      

在上述情況下,JVM将確定任何線程在建立之後都可以通路

INSTANCE

變量。 但話又說回來,正如我們所讨論的,它甚至可以使您建立對象的啟動時間加起來,甚至認為您以後需要或不再需要它。

是以,我們現在來看一下“雙重檢查鎖定”,它可以幫助我們克服我們一直在談論的所有上述問題。 在雙重檢查鎖定中,我們将首先檢視是否建立了對象,如果未建立,則隻有我們将應用同步。

companion object  {
    @Volatile private var INSTANCE : Singleton ? = null
     fun   getInstance(): Singleton {
             if ( INSTANCE  == null ){
                 synchronized ( this ) {
                     INSTANCE  = Singleton()
                 }
              }
             return INSTANCE !!
    }
}      

如您所見,我們僅在對象執行個體化階段應用同步。

您可能在想,

@Volatile

注釋在那裡做什麼。 與Java中的

volatile

關鍵字相同。 如您所知,每個線程在其堆棧中都有其變量副本,并且在建立線程時,所有可通路的值都将複制到其堆棧中。 添加

volatile

關鍵字就像是一個聲明,上面說“此變量的值可能在其他線程中更改”一樣。添加

volatile

可確定重新整理變量的值。 否則,可能會發生該線程永遠不會更新其本地緩存的情況。

作為最後的重構,我想做一個小的改動,并利用Kotlin的語言結構。 我真的不喜歡尖叫

!!

在return語句中。

companion object  {
    @Volatile private var INSTANCE : Singleton ? = null
     fun   getInstance(): Singleton {
         return INSTANCE ?: synchronized ( this ) {
              Singleton(). also  {
                 INSTANCE  = it
             }   
         }
     }
}      

是以,有了它,可以成為單例的完美方法。 您的實作可能會基于其他考慮因素而有所不同,例如您的應用程式不在多線程環境中運作,或者您可以在整個方法中使用syncize關鍵字來降低性能。

感謝您閱讀。 如果您覺得有趣,請随時拍一下(或十個?)并分享。 如果您想閱讀更多類似的文章,可以關注我。

以下是您可能感興趣的其他幾篇文章:

  • 2分鐘内學習:Kotlin中的@JvmOverloads
  • 每日Kotlin:靜态方法
  • RxJava — Flowables —什麼,何時以及如何使用它?
  • RxJava —計劃程式—什麼,何時以及如何使用它?
翻譯自: https://hackernoon.com/singleton-pattern-but-dont-get-too-comfortable-3ae69140097f

單例模式在伺服器報錯