簡體   English   中英

延遲加載 singleton:雙重檢查鎖定與按需初始化持有人習慣用法

[英]Lazy-loaded singleton: Double-checked locking vs Initialization on demand holder idiom

我需要在並發環境中延遲加載資源。 加載資源的代碼應該只執行一次。

雙重檢查鎖定(使用 JRE 5+ 和 volatile 關鍵字)和按需初始化持有者習語似乎都適合這項工作。

只看代碼,Initialization on demand holder idiom 似乎更干凈、更高效(但是,嘿,我猜這里)。 盡管如此,我還是必須注意並記錄每個單身人士的模式。 至少對我來說,很難理解為什么當場寫成這樣的代碼......

我的問題是:哪種方法更好? 為什么? 如果你的答案是否定的。 您將如何在 Java SE 環境中解決此要求?

備擇方案

我可以為此使用 CDI 而不強制它在我的整個項目中使用嗎? 有文章出來嗎?

添加另一個可能更清潔的選項。 我建議枚舉變體:

在 Java 中使用枚舉作為 singleton 的最佳方法是什么?

就可讀性而言,我會使用按需初始化的 go 持有人。 我覺得雙重檢查鎖定是一個過時且丑陋的實現。

從技術上講,通過選擇雙重檢查鎖定,您總是會在字段上產生易失性讀取,因為您可以使用按需初始化持有者習語進行正常讀取。

按需初始化持有人僅適用於 singleton,您不能擁有每個實例延遲加載的元素。 雙重檢查鎖定給必須查看 class 的每個人帶來認知負擔,因為很容易以微妙的方式出錯。 在我們將模式封裝到並發庫中的實用程序 class 之前,我們曾經遇到過各種各樣的麻煩

我們有以下選擇:

Supplier<ExpensiveThing> t1 = new LazyReference<ExpensiveThing>() {
  protected ExpensiveThing create() {
    … // expensive initialisation
  }
};

Supplier<ExpensiveThing> t2 = Lazy.supplier(new Supplier<ExpensiveThing>() {
  public ExpensiveThing get() {
    … // expensive initialisation
  }
});

就用法而言,兩者具有相同的語義。 第二種形式使內部供應商使用的任何引用在初始化后都可用於 GC。 第二種形式還支持使用 TTL/TTI 策略的超時。

按需初始化持有者始終是實現 singleton 模式的最佳實踐。 它很好地利用了 JVM 的以下特性。

  1. Static 嵌套類僅在按名稱調用時加載。
  2. class 加載機制默認受並發保護。 所以當一個線程初始化一個 class 時,其他線程等待它的完成。

此外,您不必使用 synchronize 關鍵字,它會使您的程序慢 100 倍。

我懷疑按需初始化持有人比雙重檢查鎖定(使用易失性)稍微快一些。 原因是前者在創建實例后沒有同步開銷,但后者涉及讀取 volatile (我認為)需要完整的 memory 讀取。

如果性能不是一個重要的問題,那么同步的getInstance()方法是最簡單的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM