本文首發於微信公衆號:老胡碼字java
前面介紹了Java內存模型及內存屏障相關概念,這篇文章接着介紹多線程編程另一個比較重要的概念:先行發生原則(happens-before)。編程
happens-before是判斷數據是否存在競爭,線程是否安全的主要依據,經過這個原則,咱們能夠解決併發環境下兩個操做之間是否可能存在衝突的全部問題。安全
它Java內存模型中針對兩項操做定義的偏序關係。例如操做A先行於操做B發生,那麼操做B能夠觀察到操做A所產生的全部影響,這些影響包括修改內存中共享變量的值、發送的消息,調用的方法等。微信
舉個例子:多線程
//該操做在線程1中執行
i = 1;
//該操做在線程2中執行
j = i;
//該操做在線程3中執行
i = 2
複製代碼
上述例子中有共享變量i和j,假設線程1中執行的操做「i=1」先於線程2中的操做「j=i」發生,那共享變量j的值確定爲1。併發
得出這個結論是由於根據happens-before原則:「i=1」操做能夠被觀察到,而線程3尚未開始。app
若是依舊假定操做「i=1」和操做「j=i」的先行發生關係,而線程3開始於線程1和線程2之間,線程3與線程2之間沒有先行發生關係,最後變量j的值是多少呢?結果多是1也多是2。這種狀況線程3對變量i的影響可能會被線程B觀察到,也可能不會被觀察到,當沒有被線程B觀察到的時候,線程B就會讀取到過時的舊數據,這個時候就出現了多線程安全性問題。this
Java內存模型中已經存在8條定義好的先行發生規則,這些先行發生規則不須要任何同步操做就已經存在,若是兩個操做之間的關係不在這幾天規則或則沒法經過這幾條規則推導出來,那這兩個操做就沒有順序保障,虛擬機就能夠對他們進行重排。spa
具體操做以下:線程
下面經過一個示例來看看如何經過這些規則來判斷操做之間是否具備順序性,而對於讀寫共享變量的操做來講,就是現場是否安全。
private int value = 0;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
複製代碼
這個例子是一段很簡單的代碼,假定有線程1和線程2,線程1先(時間上先)調用"setValue(1)",而後線程2調用"getValue()",最後線程2獲得的值是多少?
咱們根據前面提到的8個規則來分析下:
根據上面的分析,雖然線程1在操做時機上先於線程2,可是由於沒有任何先行發生關係,因此沒法肯定線程2中"getValue()"的值,所以這兩個線程的操做放在一塊兒是不安全的。
要解決這個問題也很簡單,一種是將setValue和getValue兩個方法都定義爲synchronized方法,這樣就能夠套用鎖規則,另一種是將value變量定義爲volatile變量,並且這裏修改value值的時候不依賴value的原值,因此就能夠套用volatile變量規則。
經過上面的分析,咱們能夠知道一個操做「時間上的先發生」不表明這個操做會「先行發生」。
那一個操做「先行發生」是否是「時間上也是先發生」呢,這個其實也是不能保證的,典型的例子就是指令重排,例以下面的例子:
//下面兩個操做在同一個線程中執行
int i = 1; //操做1
int j = 2; //操做2
複製代碼
上面的示例中,按照線程次序規則,操做1先行發生於操做2,可是操做2在實際執行過程當中極可能由於重排序而被處理器先執行,這樣也沒有影響先行發生原則的正確性,由於在這個線程中咱們沒法感知到這種變化。
時間上的前後順序與先行發生原則之間沒有基本的關係,所以咱們在衡量線程安全與否時不要關注時間順序,而是應該關注先行發生原則。