什么是線程不安全
我對線程安全的理解就是多個線程同時操作一個共享變量時會產生意料之外的情況,這種情況就是線程不安全。注意:只有寫操作才可能出現線程不安全,對共享變量只進行讀操作線程是絕對安全的。
具體線程不安全的例子有一個很經典的就是兩個線程都對一個共享變量x=0執行100次自增操作,但是x的結果並非200
因此線程不安全的條件是:多線程 + 共享變量 + 寫操作
Java的內存模型
你可能會好奇線程是如何獲取共享變量的,這就為你解答
Java線程之間的通信由Java內存模型(簡稱JMM)控制,從抽象的角度來說,JMM定義了線程和主內存之間的抽象關系。JMM的抽象示意圖如圖所示:

從圖中我們可以看到:
- 共享變量存在於主內存中,也就是堆內存
- 每一個線程都保存了一份該線程使用到的共享變量的副本
- 線程讀取共享變量優先從本地內存(也就是棧內存)中讀取,寫共享變量先寫到棧內存,再寫入堆內存
- 線程之間對共享變量的通信只能通過堆內存
以上只是Java內存模型的抽象圖,實際上線程的工作模型是這樣的,棧內存即是兩個緩沖區
接下來看一個線程不安全的例子:
假設線程A、B操作同一個共享變量X,初始兩級Cache都為空
- 線程A想要讀取X的值,由於兩級Cache都沒有命中,因此加載堆內存中的X=0,並緩存到兩個Cache中
- 線程A修改X的值為1,為為兩個Cache刷新X,再刷新到堆內存
- 線程B想要獲取X的值,一級緩存沒有獲取到,二級緩存命中,讀取到X=1
- 線程B想要修改X的值為2,先刷新自,己的一級緩存為2,再刷新二級緩存和堆內存中的X為2。目前為止一切正常,接下來重點來了
- 線程A想要讀取X的值,一級緩存命中此時X=1,但是堆內存中的X=2。可以看到線程B寫入的共享變量對X不可見,出現了線程不安全的情況。
由於Java內存機制就是這樣設計的,因此多個線程操作同一個變量會產生不安全的問題,volatitle關鍵字這是被設計出來解決這一問題的,它只能用於單個變量。
volatile解決共享變量線程不安全的策略
還是接着上面這個例子,我們這樣定義X
volatle int X=0
volatile的內存語義是:
當一個線程對volatile修飾的變量進行寫操作時,JMM會立即將該線程對應的棧內存中的副本的值刷新到堆內存中;當一個線程對volatile修飾的變量進行讀時,JMM會清空此變量的一二級緩存,直接從堆內存中讀取共享變量的值。
volatile
可以當作一個輕量級的鎖來使用,但volatile僅僅只能保證共享變量內存的可見性,不能保證操作共享變量的原子性,而鎖(如synchronized
)能保證整段鎖范圍內的代碼具有原子性。
synchronized與鎖
首先要明確的是synchronized不是鎖,鎖都是基於對象的(Object的子類),Java中的每一個對象都可以作為一個鎖。
synchronized是Java的一個關鍵字,保證臨界區內的代碼同一時刻只能有一個線程執行。
線程的執行代碼在進入synchronized代碼塊前會自動獲取內部鎖,這時候其他線程訪問該同步代碼塊時會被阻塞掛起。拿到內部鎖的線程會在正常退出同步代碼塊或者拋出異常后或者在同步塊內調用了該內置鎖資源的wait系列方法時釋放該內置鎖。內置鎖是排它鎖,也就是當一個線程獲取這個鎖后,其他線程必須等待該線程釋放鎖后才能獲取該鎖。
sysnchronized的內部鎖可以是:
- 當前類的class字節碼對象:this.getClass
- 當前類的一個實例:this
- 一個Object對象
wait和notify方法只能用於synchronized同步代碼塊內
synchronized的內存語義
與volatile
不同
進入synchronized塊的內存語義是把再synchronized塊內使用到的所有共享變量從棧內存中清空,這樣就只能從堆內存只讀取,保證了內存可見性。退出synchronized塊的內存語義是把synchronized塊內對共享變量的修改刷新到堆內存。
仔細想想,這其實也是一個加鎖和解鎖的過程,保證共享變量修改的可見性。
總結
-
volatile僅能保證單個共享變量內存的可見性,不能保證原子性。而synchronized既可保證同步塊內所有共享變量的內存可見性,又能保證其操作的原子性。
-
volatile是一個輕量級的保證內存可見性的關鍵字,實際上並沒有加鎖。因此它的性能很高。
-
synchronized是一個重量級的鎖,可以用在代碼塊、普通方法以及靜態方法上。用在代碼塊時鎖就是synchronized(~)內的對象,用在普通方法時鎖就是this,用在靜態方法時鎖就是this.getClass()
-
synchronized保證同步塊內代碼的原子性,因為要進行線程上下文切換,性能較低。不過優化過后性能也還可以。
參考
- 深入淺出Java多線程
- Java並發編程之美