Java併發編程-解決可見性與有序性問題

從可見性與有序性問題的原因着手

導致可見性問題的原因是緩存,導致有序性問題的原因是編譯優化,那麼解決二者的最直接方法就是禁用緩存和編譯優化。但是這樣程序的性能將會受到很大程度降低。

這裏較爲合理的方案是按需禁用緩存和編譯優化。Java內存模型規範了JVM如何提供按需禁用緩存和編譯優化的方法。具體包括:volatilesynchronizedfinal關鍵字和Happens-Before規則。

volatile關鍵字

“當變量聲明爲volatile類型後,編譯器與運行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內存操作一起重排序。---《Java併發編程實戰》”

當將一個變量聲明爲volatile類型,則表明對於這個變量的讀寫不使用CPU緩存,必須從內存中讀取或寫入。

從對volatile的描述可以看出,被volatile修飾的變量不寫入緩存,且不參與重排序,這就解決了可見性與有序性的問題,但這是否保證了線程安全呢?答案是否定的,volatile無法保證原子性--引起併發過程中Bug的另一個源頭。關於原子性的保證,將在後面進行討論。

Happens-Before規則

"In computer science, the happened-before relation (denoted: → ) is a relation between the result of two events, such that if one event should happen before another event, the result must reflect that, even if those events are in reality executed out of order (usually to optimize program flow)." ---wikipedia

"In Java specifically, a happens-before relationship is a guarantee that memory written to by statement A is visible to statement B, that is, that statement A completes its write before statement B starts its read." ---wikipedia

Happens-Before規則所描述的是:先後發生的兩個操作,其結果必須也體現操作的先後順序,即:前一個操作的結果對後續操作是可見的。Happens-Before規則具體有以下六項:

1.程序的順序性規則

如以下代碼,按照程序的執行順序,"x = 12;" Happens-Before 於 "v = true;",對於x的賦值一定發生在對v賦值之前,即對x的操作對後續操作可見。

class VolatileExample {
    int x = 0;
    volatile boolean v = false;
    public void writer() {
        x = 12;
        v = true;
    }
    public void reader() {
        if(v == true){
            //x=?
        }
    }
}
複製代碼

2.volatile變量規則

規則2代表針對volatile變量的寫操作,Happens-Before 於後續對這個變量的讀操作。

3.傳遞性

規則3表示:若 A Happens-Before B,且 B Happens-Before C,那麼 A Happens-Before C。 正如上面的示例代碼,假設線程A先調用writer()方法,線程B之後調用reader()方法,則:

  • 1.根據規則1,"x = 12" Happens-Before "v = true";
  • 2.根據規則2,寫變量 "v = true" Happens-Before 於讀變量 "v = true";
  • 3.再根據規則3,"x = 12" Happens-Before 讀變量 "v = true"。

這就意味着,線程A對x的寫操作對於線程B對x的讀操作是可見的,則線程B讀取的x爲12。

4.管程中鎖的規則

規則4是指一個鎖的解鎖操作 Happens-Before 於後續對這個鎖的加鎖。

管程是一種通用的同步原語,在Java中指的就是synchronized,synchronized是Java裏對管程的實現。

結合規則3,獲得鎖的線程在解鎖之前的操作 Happens-Before 解鎖操作,加上規則4的內容,則上述操作 Happens-Before 後續的加鎖操作,也就是該操作對後面獲得鎖的線程來說是可見的。

5.線程start()規則

規則5指主線程A啓動子線程B後,子線程B能夠看到A在啓動子線程B之前的操作。

也就是線程A調用線程B的start()方法,那麼該start()操作 Happens-Before 於線程B中的任意操作。

Thread B = new Thread() {
    //主線程調用B.strat()之前所有對共享變量的修改
    //此處皆可見,var爲20
}
//對共享變量賦值
var = 20;
//主線程啓動子線程
B.start();
複製代碼

6.線程join()規則

規則6描述線程間的等待關係,當主線程通過join()方法調用子線程,等待子線程完成。子線程中對共享變量的操作對主線程全部可見。即:子線程中的任意操作 Happens-Before 與join()方法的返回。

總結

1.volatile關鍵字通過禁用緩存和指令重排序的方式保證了程序運行中的可見性與有序性; 2.Happens-Before 本質上是一種描述可見性的規則,意味着若事件A Happens-Before 事件B,則A中的所有操作對B而言都是可見的,無論事件AB是否發生在同一個線程上。如事件A發生在線程1,事件B發生在線程2上,Happens-Before 規則依舊保證線程2上可以看見A事件的發生。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章