Java併發編程的暗自努力(三)競態和臨界區

當多個線程訪問了相同的資源(同一內存區、系統、文件)時可能會導致一些問題。
- 實際上,這些問題只有在一個或多個線程對這些資源進行了寫操作時,纔有可能發生,只要資源沒有發生變化,多個線程讀取相同的資源就是安全的

多個線程執行下面代碼可能會出錯:

public class Counter{
    protected long count = 0;
    public void add(long value){
        this.count = this.count + value;
    }
}

想象下線程A和B同時執行同一個Counter對象的add()方法,我們無法知道操作系統何時會在兩個線程之間切換。JVM並不是將這段代碼視爲單條指令來執行的,而是按照下面的順序:

從內存獲取 this.count 的值放到寄存器
將寄存器中的值增加value
將寄存器中的值寫回內存

觀察線程A和B交錯執行會發生什麼:

    this.count = 0;
   A:   讀取 this.count 到一個寄存器 (0)
   B:   讀取 this.count 到一個寄存器 (0)
   B:   將寄存器的值加2
   B:   回寫寄存器值(2)到內存. this.count 現在等於 2
   A:   將寄存器的值加3
   A:   回寫寄存器值(3)到內存. this.count 現在等於 3

兩個線程分別加了2和3到count變量上,兩個線程執行結束後count變量的值應該等於5。然而由於兩個線程是交叉執行的,兩個線程從內存中讀出的初始值都是0。然後各自加了2和3,並分別寫回內存。最終的值並不是期望的5,而是最後寫回內存的那個線程的值,上面例子中最後寫回內存的是線程A,但實際中也可能是線程B。如果沒有采用合適的同步機制,線程間的交叉執行情況就無法預料。

競態條件 & 臨界區

當兩個線程競爭同一資源時,如果對資源的訪問順序敏感,就稱存在競態條件。導致競態條件發生的代碼區稱作臨界區。上例中add()方法就是一個臨界區,它會產生競態條件。在臨界區中使用適當的同步就可以避免競態條件。

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