Java的執行緒安全問題

  • 2019 年 12 月 16 日
  • 筆記

Java面試時,總會被問到簡單聊一聊執行緒安全問題,這時候就要考驗,求職者對Java原理的掌握程度了,

乍一看,執行緒安全是啥啊,直接說,由於多執行緒環境,導致數據不一致等問題,就是執行緒安全問題,這可能只能打5分

Java的執行緒安全,要從Java的記憶體模型說起,

Java程式是多執行緒的,每個執行緒對於變數的操作,按照變數類型來分可能分兩種,一種是執行緒私有的局部變數,一種是執行緒共享的全局變數;

局部變數只有當前執行緒可以操作,其他執行緒根本訪問不到,所以不會出現執行緒的安全問題.

全局變數有可能被多個執行緒操作,這裡的操作可能包括:

執行緒A依賴這個變數值做判斷;

執行緒B,執行緒C都有可能修改這個變數值;

而執行緒對共享變數的操作,實際上操作的是記憶體變數的一個副本,這裡有涉及到了JMM定義的一系列對於全局記憶體和工作記憶體的幾種操作;

當然這些操作如果不特殊處理的話,就會導致一個執行緒的操作覆蓋其他執行緒的操作,因為讀取數據和寫數據,總會有時間間隔,這期間有可能變數值已經修改。

不過,Java提供了一些列的同步機制來保證執行緒安全,包括:阻塞同步和非阻塞同步,其實就是悲觀鎖和樂觀鎖的概念。

阻塞同步:提供一種鎖機制,所有對變數的操作都在鎖的基礎上進行操作,保證了同一時刻,對共享變數的操作只有一個;

非阻塞同步:提供一種樂觀鎖的機制,執行緒對變數操作不阻塞,直接操作,如果遇到競爭再進行應對;

對於保證Java執行緒的安全性,總結了幾點:可見性、原子性、有序性;

可見性典型的就是volatile,這是Java提供的最輕量級的同步機制,volatile修飾的關鍵字,只能保證可見性,也就是其他執行緒對變數的修改,當前執行緒可以立即看到,但對於變數的寫,不能保證原子性,所以還需要進行其他處理,如:多執行緒的話需加鎖或者保證只有一個執行緒會寫變數;

原子性,Java提供了鎖或者CAS,鎖就是synchronize,保證同時只有一個執行緒可以操作,保證了操作的原子性;CAS(Conmpare And Swap)原理是先比較工作記憶體和共享記憶體變數的值,再進行替換;利用底層指令來保證整個操作的原子性,不過存在ABA問題(很多樂觀鎖方案,都增加額外標誌來避免ABA問題,如Zookeeper的版本號);

有序性,這是由於Java虛擬機有指令重排的優化,在同一執行緒內的程式碼,執行順序有可能會改變,不過對於volatile和synchronize修飾的程式碼,會禁止指令重排,這種由於指令重排導致的問題,也有可能產生執行緒安全問題;

因此,總結Java執行緒安全問題就是由於多執行緒環境和Java虛擬機導致某些變數未按照我們實際期望的運行而帶來的數據不一致問題,我們應該採用Java的同步機制來避免。