天天看點

競态條件與臨界區

在同一程式中運作多個線程本身不會導緻問題,問題在于多個線程通路了相同的資源。如,同一記憶體區(變量,數組,或對象)、系統(資料庫,web services等)或檔案。實際上,這些問題隻有在一或多個線程向這些資源做了寫操作時才有可能發生,隻要資源沒有發生變化,多個線程讀取相同的資源就是安全的。

多線程同時執行下面的代碼可能會出錯:

<a href="http://ifeve.com/race-conditions-and-critical-sections/#viewsource">檢視源代碼</a>

<code>1</code>

<code>public</code> <code>class</code> <code>counter {</code>

<code>2</code>

<code>    </code><code>protected</code> <code>long</code> <code>count =</code><code>0</code><code>;</code>

<code>3</code>

<code>    </code><code>public</code> <code>void</code> <code>add(</code><code>long</code> <code>value){</code>

<code>4</code>

<code>        </code><code>this</code><code>.count =</code><code>this</code><code>.count + value;  </code>

<code>5</code>

<code>    </code><code>}</code>

<code>6</code>

<code>}</code>

想象下線程a和b同時執行同一個counter對象的add()方法,我們無法知道作業系統何時會在兩個線程之間切換。jvm并不是将這段代碼視為單條指令來執行的,而是按照下面的順序:

觀察線程a和b交錯執行會發生什麼:

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

當兩個線程競争同一資源時,如果對資源的通路順序敏感,就稱存在競态條件。導緻競态條件發生的代碼區稱作臨界區。上例中add()方法就是一個臨界區,它會産生競态條件。在臨界區中使用适當的同步就可以避免競态條件。