雙重檢查鎖模式導致空指標

2021-10-08 11:19:51 字數 3395 閱讀 5892

今天遇到乙個問題:莫名奇妙報了個空指標,後來發現原來單例模式在高併發下引起的:

雙重檢查鎖模式的一般實現:

雙重檢查鎖模式解決了單例、效能、執行緒安全問題,但是這種寫法同樣存在問題:在多執行緒的情況下,可能會出現空指標問題,出現問題的原因是jvm在例項化物件的時候會進行優化和指令重排序操作。

public class doublechecklockmode 

/*** 提供公開獲取例項介面

* @return

*/public static doublechecklockmode getinstance()}}

return instance;

}}

雙重檢查鎖模式解決了單例、效能、執行緒安全問題,但是這種寫法同樣存在問題:在多執行緒的情況下,可能會出現空指標問題,出現問題的原因是jvm在例項化物件的時候會進行優化和指令重排序操作

private singletonobject()
上面的建構函式singletonobject()jvm會對它進行指令重排序,所以執行順序可能會亂掉,但是不管是那種執行順序,jvm最後都會保證所以例項都完成例項化。如果建構函式中操作比較多時,為了提公升效率,jvm會在建構函式裡面的屬性未全部完成例項化時,就返回物件。雙重檢測鎖出現空指標問題的原因就是出現在這裡,當某個執行緒獲取鎖進行例項化時,其他執行緒就直接獲取例項使用,由於jvm指令重排序的原因,其他執行緒獲取的物件也許不是乙個完整的物件,所以在使用例項的時候就會出現空指標異常問題

public class doublechecklockmodelvolatile 

/*** 提供公開獲取例項介面

* @return

*/public static doublechecklockmodelvolatile getinstance()}}

return instance;

}}

順便複習一下設計模式:

設計模式是解決特定問題的一系列套路,是前輩們的**設計經驗的總結,具有一定的普遍性,可以反覆使用。其目的是為了提高**的可重用性、**的可讀性和**的可靠性。

設計模式的本質是物件導向設計原則的實際運用,是對類的封裝性、繼承性和多型性以及類的關聯關係和組合關係的充分理解。

專案的需求是永遠在變的,為了應對這種變化,使得我們的**能夠輕易的實現解耦和拓展

建立型模式的主要關注點是怎樣建立物件,它的主要特點是將物件的建立與使用分離。這樣可以降低系統的耦合度,使用者不需要關注物件的建立細節。

結構型模式描述如何將類或物件按某種布局組成更大的結構。它分為類結構型模式和物件結構型模式,前者採用繼承機制來組織介面和類,後者釆用組合或聚合來組合物件。

行為型模式用於描述程式在執行時複雜的流程控制,即描述多個類或物件之間怎樣相互協作共同完成單個物件都無法單獨完成的任務,它涉及演算法與物件間職責的分配。它分為類行為模式和物件行為模式,前者採用繼承機制來在類間分派行為,後者採用組合或聚合在物件間分配行為。

建立型模式

結構型模式

行為型模式

單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式

介面卡模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、**模式

模版方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、直譯器模式、狀態模式、策略模式、職責鏈模式(責任鏈模式)、訪問者模式

乙個軟體實體如類、模組和函式應該對擴充套件開放,對修改關閉

用抽象構建框架,用實現擴充套件細節;

不以改動原有類的方式來實現新需求,而是應該以實現事先抽象出來的介面(或具體類繼承抽象類)的方式來實現。

可以在不改動原有**的前提下給程式擴充套件功能,增加了程式的可擴充套件性;

同時也降低了程式的維護成本。

乙個類只允許有乙個職責,即只有乙個導致該類變更的原因。

類職責的變化往往就是導致類變化的原因:也就是說如果乙個類具有多種職責,就會有多種導致這個類變化的原因,從而導致這個類的維護變得困難;

往往在軟體開發中隨著需求的不斷增加,可能會給原來的類新增一些本來不屬於它的一些職責,從而違反了單一職責原則。如果我們發現當前類的職責不僅僅有乙個,就應該將本來不屬於該類真正的職責分離出去

不僅僅是類,函式(方法)也要遵循單一職責原則,即:乙個函式(方法)只做一件事情。如果發現乙個函式(方法)裡面有不同的任務,則需要將不同的任務以另乙個函式(方法)的形式分離出去。

提高**的可讀性,更實際性地更降低了程式出錯的風險;

有利於bug的追蹤,降低了程式的維護成本。

依賴抽象,而不是依賴實現;

抽象不應該依賴細節;細節應該依賴抽象;

高層模組不能依賴低層模組,二者都應該依賴抽象。

面向介面程式設計,而不是面向實現程式設計;

盡量不要從具體的類派生,而是以繼承抽象類或實現介面來實現;

關於高層模組與低層模組的劃分可以按照決策能力的高低進行劃分。業務層自然就處於上層模組,邏輯層和資料層自然就歸類為底層。

通過抽象來搭建框架,建立類和類的關聯,以減少類間的耦合性;

以抽象搭建的系統要比以具體實現搭建的系統更加穩定,擴充套件性更高,同時也便於維護。

子類可以擴充套件父類的功能,但不能改變父類原有的功能。也就是說,子類繼承父類時,除新增新的方法完成新增功能外,盡量不要重寫父類的方法。

多個特定的客戶端介面要好於乙個通用性的總介面。

客戶端不應該依賴它不需要實現的介面;

不建立龐大臃腫的介面,應盡量細化介面,介面中的方法應該盡量少。

注意:介面的粒度也不能太小。如果過小,則會造成介面數量過多,使設計複雜化。

避免同乙個介面裡面包含不同類職責的方法,介面責任劃分更加明確,符合高內聚低耦合的思想。

乙個物件應該對盡可能少的物件有接觸,也就是只接觸那些真正需要接觸的物件。

乙個類應該只和它的成員變數,方法的輸入,返回引數中的類作交流,而不應該引入其他的類(間接交流)。

可以良好地降低類與類之間的耦合,減少類與類之間的關聯程度,讓類與類之間的協作更加直接。

所有引用基類的地方必須能透明地使用其子類的物件,也就是說子類物件可以替換其父類物件,而程式執行效果不變。

-解讀在繼承體系中,子類中可以增加自己特有的方法,也可以實現父類的抽象方法,但是不能重寫父類的非抽象方法,否則該繼承關係就不是乙個正確的繼承關係。

可以檢驗繼承使用的正確性,約束繼承在使用上的氾濫。

菜鳥教程:

部分摘自:

雙重檢查鎖

雙重檢查鎖 簡單來說,就是在併發場景下,實現單列模式時,為解決單列模式的特性,以及效能的開銷,在例項化時先判斷物件是否已經初始化,再決定是否加鎖,同時再進行物件二次判空驗證。如以下示例 所示 注意 示例 中 volatile 關鍵字是解決由於編譯優化帶來的有序性問題可能導致程式的執行順序並不是我們所...

單例模式 雙重檢查鎖

單例模式分為餓漢式和懶漢式。餓漢式是事先分配記憶體,提前建立。這樣的方式為到位占用資源,當這種比較多時,會占用很多記憶體。懶漢式是在被呼叫的時候進行。這種在併發時又會導致問題。比較穩妥的辦法是在懶漢式的基礎上加上鎖,然後進行雙重檢查,這種springioc容器式單例也是用這種雙重檢查來避免執行緒衝突...

Java雙重檢查鎖的錯誤

1.錯誤的雙重檢查鎖實現 public class doublecheckedlocking 8 9 return instance 10 11 前面的雙重檢查鎖定示例 的第7行 instance new singleton 建立了乙個物件。這一行 可以分解為如下的3行偽 memory alloca...