天天看點

線程安全及不可變性

當多個線程同時通路同一個資源,并且其中的一個或者多個線程對這個資源進行了寫操作,才會産生<b>競态條件</b>。多個線程同時讀同一個資源不會産生競态條件。

我們可以通過建立不可變的共享對象來保證對象線上程間共享時不會被修改,進而實作線程安全。如下示例:

<a href="http://ifeve.com/thread-safety-and-immutability/#viewsource">檢視源代碼</a>

<code>01</code>

<code>public</code> <code>class</code> <code>immutablevalue{</code>

<code>02</code>

<code>    </code><code>private</code> <code>int</code> <code>value =</code><code>0</code><code>;</code>

<code>03</code>

<code>04</code>

<code>    </code><code>public</code> <code>immutablevalue(</code><code>int</code> <code>value){</code>

<code>05</code>

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

<code>06</code>

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

<code>07</code>

<code>08</code>

<code>    </code><code>public</code> <code>int</code> <code>getvalue(){</code>

<code>09</code>

<code>        </code><code>return</code> <code>this</code><code>.value;</code>

<code>10</code>

<code>11</code>

<code>}</code>

請注意immutablevalue類的成員變量<code>value</code>是通過構造函數指派的,并且在類中沒有set方法。這意味着一旦immutablevalue執行個體被建立,<code>value</code>變量就不能再被修改,這就是不可變性。但你可以通過getvalue()方法讀取這個變量的值。

(譯者注:注意,“不變”(immutable)和“隻讀”(read only)是不同的。當一個變量是“隻讀”時,變量的值不能直接改變,但是可以在其它變量發生改變的時候發生改變。比如,一個人的出生年月日是“不變”屬性,而一個人的年齡便是“隻讀”屬性,但是不是“不變”屬性。随着時間的變化,一個人的年齡會随之發生變化,而一個人的出生年月日則不會變化。這就是“不變”和“隻讀”的差別。(摘自《java與模式》第34章))

如果你需要對immutablevalue類的執行個體進行操作,可以通過得到value變量後建立一個新的執行個體來實作,下面是一個對value變量進行加法操作的示例:

<code>12</code>

<code>    </code><code>public</code> <code>immutablevalue add(</code><code>int</code> <code>valuetoadd){</code>

<code>13</code>

<code>        </code><code>return</code> <code>new</code> <code>immutablevalue(</code><code>this</code><code>.value + valuetoadd);</code>

<code>14</code>

<code>15</code>

請注意add()方法以加法操作的結果作為一個新的immutablevalue類執行個體傳回,而不是直接對它自己的value變量進行操作。

重要的是要記住,即使一個對象是線程安全的不可變對象,指向這個對象的引用也可能不是線程安全的。看這個例子:

<code>public</code> <code>void</code> <code>calculator{</code>

<code>    </code><code>private</code> <code>immutablevalue currentvalue =</code><code>null</code><code>;</code>

<code>    </code><code>public</code> <code>immutablevalue getvalue(){</code>

<code>        </code><code>return</code> <code>currentvalue;</code>

<code>    </code><code>public</code> <code>void</code> <code>setvalue(immutablevalue newvalue){</code>

<code>        </code><code>this</code><code>.currentvalue = newvalue;</code>

<code>    </code><code>public</code> <code>void</code> <code>add(</code><code>int</code> <code>newvalue){</code>

<code>        </code><code>this</code><code>.currentvalue =</code><code>this</code><code>.currentvalue.add(newvalue);</code>

calculator類持有一個指向immutablevalue執行個體的引用。注意,通過setvalue()方法和add()方法可能會改變這個引用。是以,即使calculator類内部使用了一個不可變對象,但calculator類本身還是可變的,是以calculator類不是線程安全的。換句話說:immutablevalue類是線程安全的,但使用它的類不是。當嘗試通過不可變性去獲得線程安全時,這點是需要牢記的。

要使calculator類實作線程安全,将getvalue()、setvalue()和add()方法都聲明為同步方法即可。