鎖像synchronized同步塊一樣,是一種線程同步機制,但比java中的synchronized同步塊更複雜。因為鎖(以及其它更進階的線程同步機制)是由synchronized同步塊的方式實作的,是以我們還不能完全擺脫synchronized關鍵字(譯者注:這說的是java 5之前的情況)。
以下是本文所涵蓋的主題:
<a href="http://ifeve.com/locks/#simplelock">一個簡單的鎖</a>
<a href="http://ifeve.com/locks/#lockreentrance">鎖的可重入性</a>
<a href="http://ifeve.com/locks/#lockfairness">鎖的公平性</a>
<a href="http://ifeve.com/locks/#finallyunlock">在finally語句中調用unlock()</a>
讓我們從java中的一個同步塊開始:
<a href="http://ifeve.com/locks/#viewsource">檢視源代碼</a>
<code>1</code>
<code>public</code> <code>class</code> <code>counter{</code>
<code>2</code>
<code> </code><code>private</code> <code>int</code> <code>count =</code><code>0</code><code>;</code>
<code>3</code>
<code>4</code>
<code> </code><code>public</code> <code>int</code> <code>inc(){</code>
<code>5</code>
<code> </code><code>synchronized</code><code>(</code><code>this</code><code>){</code>
<code>6</code>
<code> </code><code>return</code> <code>++count;</code>
<code>7</code>
<code> </code><code>}</code>
<code>8</code>
<code> </code><code>}</code>
<code>9</code>
<code>}</code>
可以看到在inc()方法中有一個synchronized(this)代碼塊。該代碼塊可以保證在同一時間隻有一個線程可以執行return ++count。雖然在synchronized的同步塊中的代碼可以更加複雜,但是++count這種簡單的操作已經足以表達出線程同步的意思。
以下的counter類用lock代替synchronized達到了同樣的目的:
<code>01</code>
<code>02</code>
<code> </code><code>private</code> <code>lock lock =</code><code>new</code> <code>lock();</code>
<code>03</code>
<code>04</code>
<code>05</code>
<code>06</code>
<code> </code><code>lock.lock();</code>
<code>07</code>
<code> </code><code>int</code> <code>newcount = ++count;</code>
<code>08</code>
<code> </code><code>lock.unlock();</code>
<code>09</code>
<code> </code><code>return</code> <code>newcount;</code>
<code>10</code>
<code>11</code>
lock()方法會對lock執行個體對象進行加鎖,是以所有對該對象調用lock()方法的線程都會被阻塞,直到該lock對象的unlock()方法被調用。
這裡有一個lock類的簡單實作:
<code>public</code> <code>class</code> <code>lock{</code>
<code> </code><code>private</code> <code>boolean</code> <code>islocked =</code><code>false</code><code>;</code>
<code> </code><code>public</code> <code>synchronized</code> <code>void</code> <code>lock()</code>
<code> </code><code>throws</code> <code>interruptedexception{</code>
<code> </code><code>while</code><code>(islocked){</code>
<code> </code><code>wait();</code>
<code> </code><code>islocked =</code><code>true</code><code>;</code>
<code>12</code>
<code>13</code>
<code> </code><code>public</code> <code>synchronized</code> <code>void</code> <code>unlock(){</code>
<code>14</code>
<code> </code><code>islocked =</code><code>false</code><code>;</code>
<code>15</code>
<code> </code><code>notify();</code>
<code>16</code>
<code>17</code>
java中的synchronized同步塊是可重入的。這意味着如果一個java線程進入了代碼中的synchronized同步塊,并是以獲得了該同步塊使用的同步對象對應的管程上的鎖,那麼這個線程可以進入由同一個管程對象所同步的另一個java代碼塊。下面是一個例子:
<code>public</code> <code>class</code> <code>reentrant{</code>
<code> </code><code>public</code> <code>synchronized</code> <code>outer(){</code>
<code> </code><code>inner();</code>
<code> </code><code>public</code> <code>synchronized</code> <code>inner(){</code>
<code> </code><code>//do something</code>
注意outer()和inner()都被聲明為synchronized,這在java中和synchronized(this)塊等效。如果一個線程調用了outer(),在outer()裡調用inner()就沒有什麼問題,因為這兩個方法(代碼塊)都由同一個管程對象(”this”)所同步。如果一個線程已經擁有了一個管程對象上的鎖,那麼它就有權通路被這個管程對象同步的所有代碼塊。這就是可重入。線程可以進入任何一個它已經擁有的鎖所同步着的代碼塊。
前面給出的鎖實作不是可重入的。如果我們像下面這樣重寫reentrant類,當線程調用outer()時,會在inner()方法的lock.lock()處阻塞住。
<code>public</code> <code>class</code> <code>reentrant2{</code>
<code> </code><code>lock lock =</code><code>new</code> <code>lock();</code>
<code> </code><code>public</code> <code>outer(){</code>
調用outer()的線程首先會鎖住lock執行個體,然後繼續調用inner()。inner()方法中該線程将再一次嘗試鎖住lock執行個體,結果該動作會失敗(也就是說該線程會被阻塞),因為這個lock執行個體已經在outer()方法中被鎖住了。
兩次lock()之間沒有調用unlock(),第二次調用lock就會阻塞,看過lock()實作後,會發現原因很明顯:
<code> </code><code>boolean</code> <code>islocked =</code><code>false</code><code>;</code>
<code> </code><code>...</code>
一個線程是否被允許退出lock()方法是由while循環(自旋鎖)中的條件決定的。目前的判斷條件是隻有當islocked為false時lock操作才被允許,而沒有考慮是哪個線程鎖住了它。
為了讓這個lock類具有可重入性,我們需要對它做一點小的改動:
<code> </code><code>thread lockedby =</code><code>null</code><code>;</code>
<code> </code><code>int</code> <code>lockedcount =</code><code>0</code><code>;</code>
<code> </code><code>thread callingthread =</code>
<code> </code><code>thread.currentthread();</code>
<code> </code><code>while</code><code>(islocked && lockedby != callingthread){</code>
<code> </code><code>lockedcount++;</code>
<code> </code><code>lockedby = callingthread;</code>
<code> </code><code>}</code>
<code>18</code>
<code>19</code>
<code> </code><code>if</code><code>(thread.curentthread() ==</code>
<code>20</code>
<code> </code><code>this</code><code>.lockedby){</code>
<code>21</code>
<code> </code><code>lockedcount--;</code>
<code>22</code>
<code>23</code>
<code> </code><code>if</code><code>(lockedcount ==</code><code>0</code><code>){</code>
<code>24</code>
<code> </code><code>islocked =</code><code>false</code><code>;</code>
<code>25</code>
<code> </code><code>notify();</code>
<code>26</code>
<code> </code><code>}</code>
<code>27</code>
<code>28</code>
<code>29</code>
<code>30</code>
<code>31</code>
注意到現在的while循環(自旋鎖)也考慮到了已鎖住該lock執行個體的線程。如果目前的鎖對象沒有被加鎖(islocked = false),或者目前調用線程已經對該lock執行個體加了鎖,那麼while循環就不會被執行,調用lock()的線程就可以退出該方法(譯者注:“被允許退出該方法”在目前語義下就是指不會調用wait()而導緻阻塞)。
除此之外,我們需要記錄同一個線程重複對一個鎖對象加鎖的次數。否則,一次unblock()調用就會解除整個鎖,即使目前鎖已經被加鎖過多次。在unlock()調用沒有達到對應lock()調用的次數之前,我們不希望鎖被解除。
現在這個lock類就是可重入的了。
如果用lock來保護臨界區,并且臨界區有可能會抛出異常,那麼在finally語句中調用unlock()就顯得非常重要了。這樣可以保證這個鎖對象可以被解鎖以便其它線程能繼續對其加鎖。以下是一個示例:
<code>lock.lock();</code>
<code>try</code><code>{</code>
<code> </code><code>//do critical section code,</code>
<code> </code><code>//which may throw exception</code>
<code>}</code><code>finally</code> <code>{</code>
<code> </code><code>lock.unlock();</code>
這個簡單的結構可以保證當臨界區抛出異常時lock對象可以被解鎖。如果不是在finally語句中調用的unlock(),當臨界區抛出異常時,lock對象将永遠停留在被鎖住的狀态,這會導緻其它所有在該lock對象上調用lock()的線程一直阻塞。