本博客系列是學習併發編程過程當中的記錄總結。因爲文章比較多,寫的時間也比較散,因此我整理了個目錄貼(傳送門),方便查閱。html
併發編程系列博客傳送門java
本文是《深刻Java虛擬機》的部分讀書筆記編程
若是Java內存模型中全部的有序性都僅靠volatile和synchronized來完成,那麼有不少操做都將會變得很是囉嗦。安全
可是咱們在編寫Java併發代碼的時候並無察覺到這一點,這是由於Java語言中有一個「先行發生」(Happens-Before)的原則。這個原則很是重要,它是判斷數據是否存在競爭,線程是否安全的很是有用的手段。依賴這個原則,咱們能夠經過幾條簡單規則一攬子解決併發環境下兩個操做之間是否可能存在衝突的全部問題,而不須要陷入Java內存模型苦澀難懂的定義之中。併發
先行發生是Java內存模型中定義的兩項操做之間的偏序關係,好比說操做A先行發生於操做B,其實就是說在發生操做B以前,操做A產生的影響能被操做B觀察到,「影響」包括修改了內存中共享變量的值、發送了消息、調用了方法等。app
下面是Java內存模型下一些「自然的」先行發生關係,這些先行發生關係無須任何同步器協助就已經存在,能夠在編碼中直接使用。若是兩個操做之間的關係不在此列,而且沒法從下列規則推導出來,則它們就沒有順序性保障,虛擬機能夠對它們隨意地進行重排序。函數
Java語言無須任何同步手段保障就能成立的先行發生規則有且只有上面這些,下面演示一下如何使用這些規則去斷定操做間是否具有順序性,對於讀寫共享變量的操做來講,就是線程是否安全。讀者還能夠從下面這個例子中感覺一下「時間上的前後順序」與「先行發生」之間有什麼不一樣。學習
private int value = 0; pubilc void setValue(int value){ this.value = value; } public int getValue(){ return value; }
上面的代碼顯示的是一組再普通不過的getter/setter方法,假設存在線程A和B,線程A先(時間上的前後)調用了setValue(1),而後線程B調用了同一個對象的getValue(),那麼線程B收到的返回值是什麼?this
咱們依次分析一下先行發生原則中的各項規則。因爲兩個方法分別由線程A和B調用,不在一個線程中,因此程序次序規則在這裏不適用;因爲沒有同步塊,天然就不會發生lock和unlock操做,因此管程鎖定規則不適用;因爲value變量沒有被volatile關鍵字修飾,因此volatile變量規則不適用;後面的線程啓動、終止、中斷規則和對象終結規則也和這裏徹底沒有關係。由於沒有一個適用的先行發生規則,因此最後一條傳遞性也無從談起,所以咱們能夠斷定,儘管線程A在操做時間上先於線程B,可是沒法肯定線程B中getValue()方法的返回結果,換句話說,這裏面的操做不是線程安全的。編碼
咱們至少有兩種比較簡單的方案能夠選擇:要麼把getter/setter方法都定義爲synchronized方法,這樣就能夠套用管程鎖定規則;要麼把value定義爲volatile變量,因爲setter方法對value的修改不依賴value的原值,知足volatile關鍵字使用場景,這樣就能夠套用volatile變量規則來實現先行發生關係。
經過上面的例子,咱們能夠得出結論:一個操做「時間上的先發生」不表明這個操做會是「先行發生」。那若是一個操做「先行發生」,是否就能推導出這個操做一定是「時間上的先發生」呢?很遺憾,這個推論也是不成立的。
上面兩個例子綜合起來證實了一個結論:時間前後順序與先行發生原則之間基本沒有因果關係,因此咱們衡量併發安全問題的時候不要受時間順序的干擾,一切必須以先行發生原則爲準。