单例模式到Java内存模型
先說單例模式:
經典的單例模式實現:
餓漢式:
public class Singleton {private static Singleton instance = new Singleton();public static Singleton getInstance(){return instance;} }懶漢式:
public class Singleton {private static Singleton instance = null;synchronized public static Singleton getInstance(){if(instance ==null){instance = new Singleton();}return instance;} }這兩種都是可以安全運行在多線程下的。但是每一個都有點缺點,對于第一種如果這個單例的初始化需要很多內存和時間,我們希望用到時在初始化,沒有用到就不初始化。對于第二種我們,其實只需要在第一次初始化時需要避免線程沖突,其他時候都可以直接返回的,而第二種的實現則變成了完全的串行(因為每一個操作都需要獲得對象鎖),非常大的降低了并發度。
我們嘗試以下改進:
一個好的方式是DCL(double-checked?locking),這種方式的實現如下:
public class Singleton {private static Singleton instance = null;public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null)instance = new Singleton();}}return instance;} }這樣看起來是完美的解決方案,確實DCL是一個很好的解決思想,在C下其能很好的運行,但在Java下就會有問題了。這全怪Java的JMM(Java內存模型)。
在執行到instance = new Singleton()這里時,由于Java內存的“無序寫入”,
可能的執行順序是這樣的:
mem?=?allocate();?????????????//Allocate?memory?for?Singleton?object.
?instance?=?mem;???????????????//Note?that?instance?is?now?non-null,?but?has?not?been?initialized.
?ctorSingleton(instance);??????//Invoke?constructor?for?Singleton?passinginstance.
即為instance分配內存,標記instance不為空,初始化instance。
這樣會導致,一個線程剛標記完,還沒有初始化賦值給instance,就釋放了鎖,然后另一個線程進入鎖,判斷不為空,釋放鎖,返回instance,這時顯然是錯的。這里出現這中錯誤的原因是instance = new instance();并沒有真正的執行完,就釋放了鎖,我實在不能理解這樣設計的原因,但很好的是在JDK1.5之后,已不存在這種問題了DCL這個可以很好的運行。但我們還是有必要繼續討論JDK1.5之前如何實現的。
可以在instance返回之前加一個步奏,確定其確實初始化了。
public class Singleton {private static Singleton instance = null;public static Singleton getInstance() {if (instance == null) {Singleton temp = instance;synchronized (Singleton.class) {if (temp == null)instance = new Singleton();}instance = temp;}return instance;} }這樣就很好的解決這個問題了。但是代碼量和可閱讀性已經陡然上升了,那么有沒有更好的方法呢?是有的,利用類加載機制來實現,延遲初始化。
public class Singleton {private Singleton() {}private static class SingletonHolder {private static Singleton instance = new Singleton();}public static Singleton getInstance() {return SingletonHolder.instance;} }這里補充一點類加載的知識,類加載分為一個步驟,加載->驗證->準備->解析->初始化->使用->卸載。
Java中對何時初始化一個類有嚴格的說明,這里涉及到的一個規則是當類的靜態域,或靜態方法被引用的時候,必須對聲明這個靜態域或方法的類進行初始化。至于說明時候對類進行加載,這個有兩種形式:餓加載(只要有其他類引用了它就加載),懶加載(初始化的時候才加載)。具體JVM對這點的實現不同。
所以當懶漢式能保證用到的時候才進行初始化,而餓漢試則是在加載時就初始化了。
?
轉載于:https://www.cnblogs.com/chaiwentao/p/4959648.html
總結
以上是生活随笔為你收集整理的单例模式到Java内存模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 五子棋游戏设计VHDL语言
- 下一篇: OC-封装、继承、多态