天天看點

Java中的鎖

鎖像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中的一個同步塊開始:

可以看到在inc()方法中有一個synchronized(this)代碼塊。該代碼塊可以保證在同一時間隻有一個線程可以執行return

++count。雖然在synchronized的同步塊中的代碼可以更加複雜,但是++count這種簡單的操作已經足以表達出線程同步的意思。

以下的counter類用lock代替synchronized達到了同樣的目的:

lock()方法會對lock執行個體對象進行加鎖,是以所有對該對象調用lock()方法的線程都會被阻塞,直到該lock對象的unlock()方法被調用。

這裡有一個lock類的簡單實作:

<code></code>

這個線程會重新去檢查islocked條件以決定目前是否可以安全地繼續執行還是需要重新保持等待,而不是認為線程被喚醒了就可以安全地繼續執行了。如果

islocked為false,目前線程會退出while(islocked)循環,并将islocked設回true,讓其它正在調用lock()方法

的線程能夠在lock執行個體上加鎖。

java中的synchronized同步塊是可重入的。這意味着如果一個java線程進入了代碼中的synchronized同步塊,并是以獲得了該同

步塊使用的同步對象對應的管程上的鎖,那麼這個線程可以進入由同一個管程對象所同步的另一個java代碼塊。下面是一個例子:

注意outer()和inner()都被聲明為synchronized,這在java中和synchronized(this)塊等效。如果一個

線程調用了outer(),在outer()裡調用inner()就沒有什麼問題,因為這兩個方法(代碼塊)都由同一個管程對象(”this”)所同步。

如果一個線程已經擁有了一個管程對象上的鎖,那麼它就有權通路被這個管程對象同步的所有代碼塊。這就是可重入。線程可以進入任何一個它已經擁有的鎖所同步

着的代碼塊。

前面給出的鎖實作不是可重入的。如果我們像下面這樣重寫reentrant類,當線程調用outer()時,會在inner()方法的lock.lock()處阻塞住。

調用outer()的線程首先會鎖住lock執行個體,然後繼續調用inner()。inner()方法中該線程将再一次嘗

試鎖住lock執行個體,結果該動作會失敗(也就是說該線程會被阻塞),因為這個lock執行個體已經在outer()方法中被鎖住了。

兩次lock()之間沒有調用unlock(),第二次調用lock就會阻塞,看過lock()實作後,會發現原因很明顯:

一個線程是否被允許退出lock()方法是由while循環(自旋鎖)中的條件決定的。目前的判斷條件是隻有當islocked為false時lock操作才被允許,而沒有考慮是哪個線程鎖住了它。

為了讓這個lock類具有可重入性,我們需要對它做一點小的改動:

這個簡單的結構可以保證當臨界區抛出異常時lock對象可以被解鎖。如果不是在finally語句中調用的unlock(),當臨界區抛出異常時,lock對象将永遠停留在被鎖住的狀态,這會導緻其它所有在該lock對象上調用lock()的線程一直阻塞。