您好,登錄后才能下訂單哦!
這篇文章跟大家分析一下“如何用JAVA語言分析雙重檢查鎖定”。內容詳細易懂,對“如何用JAVA語言分析雙重檢查鎖定”感興趣的朋友可以跟著小編的思路慢慢深入來閱讀一下,希望閱讀后能夠對大家有所幫助。下面跟著小編一起深入學習“如何用JAVA語言分析雙重檢查鎖定”的知識吧。
在程序開發中,有時需要推遲一些高開銷的對象初始化操作,并且只有在使用這些對象時才進行初始化,此時可以采用雙重檢查鎖定來延遲對象初始化操作。雙重檢查鎖定是設計用來減少并發系統中競爭和同步開銷的一種軟件設計模式,在普通單例模式的基礎上,先判斷對象是否已經被初始化,再決定要不要加鎖。盡管雙重檢查鎖定解決了普通單例模式的在多線程環境中易出錯和線程不安全的問題,但仍然存在一些隱患。下面以JAVA語言源代碼為例,分析雙重檢查鎖定缺陷產生的原因以及修復方法。
雙重檢查鎖定在單線程環境中并無影響,在多線程環境下,由于線程隨時會相互切換執行,在指令重排的情況下,對象未實例化完全,導致程序調用出錯。
示例源于Samate Juliet Test Suite for Java v1.3 (https://samate.nist.gov/SARD/testsuite.php),源文件名:CWE609_Double_Checked_Locking__Servlet_01.java。
上述代碼行23行-38行,程序先判斷 stringBad
是否為 null,如果不是則直接返回該 String
對象,這樣避免了進入 synchronized
塊所需要花費的資源。當 stringBad
為 null 時,使用 synchronized
關鍵字在多線程環境中避免多次創建 String
對象。在代碼實際運行時,以上代碼仍然可能發生錯誤。
對于第33行,創建 stringBad
對象和賦值操作是分兩步執行的。但 JVM 不保證這兩個操作的先后順序。當指令重排序后,JVM 會先賦值指向了內存地址,然后再初始化 stringBad
對象。如果此時存在兩個線程,兩個線程同時進入了第27行。線程1首先進入了 synchronized
塊,由于 stringBad
為 null,所以它執行了第33行。當 JVM 對指令進行了重排序,JVM 先分配了實例的空白內存,并賦值給 stringBad
,但這時 stringBad
對象還未實例化,然后線程1離開了 synchronized
塊。當線程2進入 synchronized
塊時,由于 stringBad
此時不是 null ,直接返回了未被實例化的對象(僅有內存地址值,對象實際未初始化)。后續線程2調用程序對 stringBad
對象進行操作時,此時的對象未被初始化,于是錯誤發生。
使用360代碼衛士對上述示例代碼進行檢測,可以檢出“雙重檢查鎖定”缺陷,顯示等級為中。在代碼行第27行報出缺陷,如圖1所示:
圖1:“雙重檢查鎖定”的檢測示例
volatile
關鍵字來對單例變量 stringBad
進行修飾。 volatile
作為指令關鍵字確保指令不會因編譯器的優化而省略,且要求每次直接讀值。volatile
關鍵字就可以從語義上解決這個問題,值得關注的是 volatile
的禁止指令重排序優化功能在 Java 1.5 后才得以實現,因此1.5 前的版本仍然是不安全的,即使使用了 volatile
關鍵字。使用360代碼衛士對修復后的代碼進行檢測,可以看到已不存在“雙重檢查鎖定”缺陷。如圖2:
要避免雙重檢查鎖定,需要注意以下幾點:
(1)使用 volatile 關鍵字避免指令重排序,但這個解決方案需要 JDK5 或更高版本,因為從JDK5 開始使用新的 JSR-133 內存模型規范,這個規范增強了 volatile 的語義。
(2)基于類初始化的解決方案。
JVM在類的初始化階段(即在Class被加載后,且被線程使用之前),會執行類的初始化。在執行類的初始化期間,JVM會去獲取一個鎖。這個鎖可以同步多個線程對同一個類的初始化。
關于如何用JAVA語言分析雙重檢查鎖定就分享到這里啦,希望上述內容能夠讓大家有所提升。如果想要學習更多知識,請大家多多留意小編的更新。謝謝大家關注一下億速云網站!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。