双重检查锁模式导致空指针
今天遇到一個問題:莫名奇妙報了個空指針,后來發現原來單例模式在高并發下引起的:
雙重檢查鎖模式的一般實現:
雙重檢查鎖模式解決了單例、性能、線程安全問題,但是這種寫法同樣存在問題:在多線程的情況下,可能會出現空指針問題,出現問題的原因是JVM在實例化對象的時候會進行優化和指令重排序操作。
雙重檢查鎖模式的一般實現
public class DoubleCheckLockMode {private static DoubleCheckLockMode instance;/*** 私有化構造函數*/private DoubleCheckLockMode(){}/*** 提供公開獲取實例接口* @return*/public static DoubleCheckLockMode getInstance(){// 第一次判斷,如果這里為空,不進入搶鎖階段,直接返回實例if (instance == null) {synchronized (DoubleCheckLockMode.class) {// 搶到鎖之后再次判斷是否為空if (instance == null) {instance = new DoubleCheckLockMode();}}}return instance;} }雙重檢查鎖模式解決了單例、性能、線程安全問題,但是這種寫法同樣存在問題:在多線程的情況下,可能會出現空指針問題,出現問題的原因是JVM在實例化對象的時候會進行優化和指令重排序操作
什么是指令重排?
private SingletonObject(){// 第一步int x = 10;// 第二步int y = 30;// 第三步Object o = new Object(); }上面的構造函數SingletonObject(),JVM?會對它進行指令重排序,所以執行順序可能會亂掉,但是不管是那種執行順序,JVM?最后都會保證所以實例都完成實例化。?如果構造函數中操作比較多時,為了提升效率,JVM?會在構造函數里面的屬性未全部完成實例化時,就返回對象。雙重檢測鎖出現空指針問題的原因就是出現在這里,當某個線程獲取鎖進行實例化時,其他線程就直接獲取實例使用,由于JVM指令重排序的原因,其他線程獲取的對象也許不是一個完整的對象,所以在使用實例的時候就會出現空指針異常問題。
雙重檢查鎖模式優化
要解決雙重檢查鎖模式帶來空指針異常的問題,只需要使用volatile關鍵字,volatile關鍵字嚴格遵循happens-before原則,即:在讀操作前,寫操作必須全部完成
public class DoubleCheckLockModelVolatile {/*** 添加volatile關鍵字,保證在讀操作前,寫操作必須全部完成*/private static volatile DoubleCheckLockModelVolatile instance;/*** 私有化構造函數*/private DoubleCheckLockModelVolatile(){}/*** 提供公開獲取實例接口* @return*/public static DoubleCheckLockModelVolatile getInstance(){if (instance == null) {synchronized (DoubleCheckLockModelVolatile.class) {if (instance == null) {instance = new DoubleCheckLockModelVolatile();}}}return instance;} }順便復習一下設計模式:
一、設計模式
1.1 設計模式是什么?
1.2 為什么要使用設計模式?
項目的需求是永遠在變的,為了應對這種變化,使得我們的代碼能夠輕易的實現解耦和拓展
1.3 設計模式類型
- 創建型模式
創建型模式的主要關注點是怎樣創建對象,它的主要特點是將對象的創建與使用分離。這樣可以降低系統的耦合度,使用者不需要關注對象的創建細節。
- 結構型模式
結構型模式描述如何將類或對象按某種布局組成更大的結構。它分為類結構型模式和對象結構型模式,前者采用繼承機制來組織接口和類,后者釆用組合或聚合來組合對象。
- 行為型模式
行為型模式用于描述程序在運行時復雜的流程控制,即描述多個類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務,它涉及算法與對象間職責的分配。它分為類行為模式和對象行為模式,前者采用繼承機制來在類間分派行為,后者采用組合或聚合在對象間分配行為。
| 單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式 | 適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式 | 模版方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式、狀態模式、策略模式、職責鏈模式(責任鏈模式)、訪問者模式 |
二、面向對象設計的六大設計原則
2.1 開閉原則
一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉
- 解讀
- 優點
2.2 單一職責原則
一個類只允許有一個職責,即只有一個導致該類變更的原因。
- 解讀
類職責的變化往往就是導致類變化的原因:也就是說如果一個類具有多種職責,就會有多種導致這個類變化的原因,從而導致這個類的維護變得困難;
往往在軟件開發中隨著需求的不斷增加,可能會給原來的類添加一些本來不屬于它的一些職責,從而違反了單一職責原則。如果我們發現當前類的職責不僅僅有一個,就應該將本來不屬于該類真正的職責分離出去;
不僅僅是類,函數(方法)也要遵循單一職責原則,即:一個函數(方法)只做一件事情。如果發現一個函數(方法)里面有不同的任務,則需要將不同的任務以另一個函數(方法)的形式分離出去。
- 優點
2.3 依賴倒置原則
- 解讀
- 優點
- 里氏替換原則
子類可以擴展父類的功能,但不能改變父類原有的功能。也就是說,子類繼承父類時,除添加新的方法完成新增功能外,盡量不要重寫父類的方法。
2.4 接口隔離原則
多個特定的客戶端接口要好于一個通用性的總接口。
- 解讀
注意:接口的粒度也不能太小。如果過小,則會造成接口數量過多,使設計復雜化。
- 優點
避免同一個接口里面包含不同類職責的方法,接口責任劃分更加明確,符合高內聚低耦合的思想。
2.5 迪米特法則(最少知道原則)
一個對象應該對盡可能少的對象有接觸,也就是只接觸那些真正需要接觸的對象。
- 解讀
一個類應該只和它的成員變量,方法的輸入,返回參數中的類作交流,而不應該引入其他的類(間接交流)。
- 優點
可以良好地降低類與類之間的耦合,減少類與類之間的關聯程度,讓類與類之間的協作更加直接。
2.6 組合聚合復用原則
所有引用基類的地方必須能透明地使用其子類的對象,也就是說子類對象可以替換其父類對象,而程序執行效果不變。
-解讀
在繼承體系中,子類中可以增加自己特有的方法,也可以實現父類的抽象方法,但是不能重寫父類的非抽象方法,否則該繼承關系就不是一個正確的繼承關系。
- 優點
可以檢驗繼承使用的正確性,約束繼承在使用上的泛濫。
關于設計模式更多的可以參考:
菜鳥教程:https://www.runoob.com/design-pattern/singleton-pattern.html
部分摘自:https://www.cnblogs.com/vandusty/p/11444293.html
?
?
?
?
?
總結
以上是生活随笔為你收集整理的双重检查锁模式导致空指针的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: QPS/TPS/并发量/系统吞吐量
- 下一篇: PostgreSQL mysql 兼容性