Java中的双重检查锁定
在本文中,我們將介紹在RxJava中創建Singleton對象的一些技術。 最重要的是,我們將學習Java中的雙重檢查鎖定 。
Java中的Singleton模式是一種創新模式。 隨著時間的流逝,人們開始關注Singleton模式的使用和實現。 這是由于單例的實現和使用方式存在一些非常根本的問題所致。
Java中的單例模式具有多種功能,例如:
使用Java創建Singleton模式的三種基本方法。 我將列出所有這些內容,并告訴您單例模式是如何隨著時間演變的,以及為什么雙重檢查鎖定是當前最好的方法。
這是Java中Singleton模式的基本實現。
class Example{ ????private Example mExample = null ; ??public Example getInstance (){ if (mExample == null ) mExample = new Example (); return mExample; } // rest of the code... }注意:構造函數在所有實現中都是私有的。
此代碼將在多線程上下文中失敗。 多個線程可以調用getInstance()方法并最終創建Singleton的多個實例。 這是不希望的行為。 Singleton的基本屬性是,JVM中應該只有該類的單個實例。
優點:
- 易于閱讀。
- 在單線程應用程序中可以正常工作。
缺點:
- 在多線程上下文中將失敗。
- 多個線程可以創建此類的多個實例。
- 將無法達到Singletons的目的。
一些聰明的人想到了創建單例的優雅解決方案。 我們使用synced關鍵字來防止線程同時訪問getInstance()方法。
class Example{ ????private Example mExample = null ; ??public synchronized Example getInstance (){ if (mExample == null ) mExample = new Example (); return mExample; } // rest of the code... }通過使用synced關鍵字,我們是JVM,一次只能讓一個字段訪問此方法。 這解決了多線程上下文的問題。
如果您看一下上面的代碼,您會注意到我們已經使整個方法同步。 每個訪問該方法的線程都將首先獲取一個鎖。
同步或獲取鎖是一種昂貴的方法。 確實會降低應用程序的性能。 如果您想進一步了解同步的性能開銷,那么這個SO答案將是一個好的開始。
即使所有線程都獲得了鎖定,它也只是需要鎖定的第一個線程。 初始化對象后,空檢查足以在線程之間維護單個實例。
優點:
- 確實很好地處理了多線程環境。
- 容易明白。
缺點:
- 每當線程嘗試訪問該方法時,獲取不必要的鎖定。
- 鎖定確實非常昂貴,并且許多線程都想獲得一個鎖定,這會導致嚴重的性能開銷。
在先前的方法中,我們將整個方法同步為線程安全的。 但是同步不僅適用于方法。 我們也可以創建同步塊。
在此方法中,我們將創建一個同步塊而不是整個方法。
class Example{ ????private Example mExample = null ; ??public Example getInstance (){ if (mExample == null ){ synchronized (Example. class ){ if (mExample == null ) mExample = new Example (); } } return mExample; } // rest of the code... }這是步驟順序:
- 第一個線程調用getInstance()方法。
- 它檢查實例是否為空(對于第一個線程,它為)。
- 然后,它獲取一個鎖。
- 檢查該字段是否仍然為空?
- 如果是,它將創建該類的新實例并初始化該字段。 最后,返回實例。
- 其余線程不需要獲取鎖定,因為字段已經初始化,因此降低了同步命中率!
注意同步塊之前和之后的多個空檢查。 因此,名稱為double check lock 。
優點:
- 在多線程環境中工作。
- 比同步方法具有更好的性能。
- 只有第一個線程需要獲取鎖。
- 以上方法中最好的。
缺點:
- 一開始,雙重null檢查可能會造成混淆。
- 不行!!
是的,上述方法存在一個細微問題。 它并不總是有效。
問題在于,編譯器對程序的感覺與人眼的感覺截然不同。 根據我們的邏輯,首先,應創建Example類的實例,然后將其分配給mExample字段。
但是不能保證此操作順序。 編譯器可以自由地對語句進行重新排序,只要它不影響最終結果即可。
因此,例如,您可能最終會將部分初始化的對象分配給mExample字段。 然后其他線程將對象視為非空。 這導致線程使用部分初始化的對象,這可能導致崩潰 !
如今,編譯器對您的代碼進行了某些優化,使他們可以自由地對語句進行重新排序。 當編譯器內聯構造函數調用時,可能會發生重新排序。
Doug Lea寫了一篇有關基于編譯器的重新排序的詳細文章。
Paul Jakubik發現了使用雙重檢查鎖定無法正常工作的示例。
如果上述所有方法都容易失敗,那么我們還剩下什么?
在J2SE 5.0中,Java的內存模型發生了很大變化。 volatile關鍵字現在可以解決上述問題。
Java平臺不允許將易失字段的讀取或寫入與之前的任何讀取或寫入重新排序。
class Example{ ????private volatile Example mExample = null ; ??public Example getInstance (){ if (mExample == null ){ synchronized (Example. class ){ if (mExample == null ) mExample = new Example (); } } return mExample; } // rest of the code... }當心:僅在JDK 5及更高版本中有效。 對于android開發人員,您最好選擇使用Java 7及更高版本的Java。
希望您覺得本文有用。 如果您愿意,請在下面的評論部分中告訴我,我很樂意寫更多這樣的概念文章。
翻譯自: https://www.javacodegeeks.com/2019/09/double-check-locking-java.html
總結
以上是生活随笔為你收集整理的Java中的双重检查锁定的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不记得路由器密码重置方法
- 下一篇: it编年史_Java的编年史和低延迟