您好,登錄后才能下訂單哦!
本篇文章為大家展示了double-checked locking與單例模式的示例分析,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
單例模式確保某個類只有一個實例,而且自行實例化并向整個系統提供這個實例。在計算機系統中,線程池、緩存、日志對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。
單例模式有如下實現方式:
這種方式稱為延遲初始化,但是在多線程的情況下會失效,于是使用同步鎖,給getInstance() 方法加鎖:
同步是需要開銷的,我們只需要在初始化的時候同步,而正常的代碼執行路徑不需要同步,于是有了雙重檢查加鎖(DCL):
這樣一種設計可以保證只產生一個實例,并且只會在初始化的時候加同步鎖,看似精妙絕倫,但卻會引發另一個問題,這個問題由指令重排序引起。
指令重排序是為了優化指令,提高程序運行效率。指令重排序包括編譯器重排序和運行時重排序。JVM規范規定,指令重排序可以在不影響單線程程序執行結果前提下進行。例如 instance = new Singleton() 可分解為如下偽代碼:
但是經過重排序后如下:
將第2步和第3步調換順序,在單線程情況下不會影響程序執行的結果,但是在多線程情況下就不一樣了。線程A執行了instance = memory(這對另一個線程B來說是可見的),此時線程B執行外層 if (instance == null),發現instance不為空,隨即返回,但是得到的卻是未被完全初始化的實例,在使用的時候必定會有風險,這正是雙重檢查鎖定的問題所在!
鑒于DCL的缺陷,便有了修訂版:
修訂版試圖引進局部變量和第二個synchronized來解決指令重排序的問題。但是,Java語言規范雖然規定了同步代碼塊內的代碼必須在對象鎖釋放之前執行完畢,卻沒有規定同步代碼塊之外的代碼不能在對象鎖釋放之前執行,也就是說 instance = temp 可能會在編譯期或者運行期移到里層的synchronized內,于是又會引發跟DCL一樣的問題。
在JDK1.5之后,可以使用volatile變量禁止指令重排序,讓DCL生效:
volatile的另一個語義是保證變量修改的可見性。
單例模式還有如下實現方式:
這種方式稱為延遲初始化占位(Holder)類模式。該模式引進了一個靜態內部類(占位類),在內部類中提前初始化實例,既保證了Singleton實例的延遲初始化,又保證了同步。這是一種提前初始化(惡漢式)和延遲初始化(懶漢式)的綜合模式。
至此,正確的單例模式有三種實現方式:
1.提前初始化。
2.雙重檢查鎖定 + volatile。
3.延遲初始化占位類模式。
上述內容就是double-checked locking與單例模式的示例分析,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。