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