在java.util.concurrent.locks包中有很多Lock的實作類別,常用的有ReentrantLock、ReadWriteLock(實作類別ReentrantReadWriteLock),其實現都依賴java.util.concurrent.AbstractQueuedSynchronizer類,實現思路都大同小異,因此我們以ReentrantLock作為講解切入點。 1. ReentrantLock的調用過程
經過觀察ReentrantLock把所有Lock介面的操作都委派到一個Sync類上,該類繼承了AbstractQueuedSynchronizer:
Java代碼 static abstract class Sync extends AbstractQueuedSynchronizer
Sync又有兩個子類: Java代碼 final static class NonfairSync extends Sync Java代碼 final static class FairSync extends Sync
顯然是為了支援公平鎖和非公平鎖而定義,預設情況下為非公平鎖。
先理一下Reentrant.lock()方法的調用過程(預設非公平鎖):
這些討厭的Template模式導致很難直觀的看到整個調用過程,其實通過上面調用過程及AbstractQueuedSynchronizer的注釋可以發現,AbstractQueuedSynchronizer中抽象了絕大多數Lock的功能,而只把tryAcquire方法延遲到子類中實現。tryAcquire方法的語義在於用具體子類判斷請求線程是否可以獲得鎖,無論成功與否AbstractQueuedSynchronizer都將處理後面的流程。 2. 鎖實現(加鎖)
簡單說來,AbstractQueuedSynchronizer會把所有的請求線程構成一個CLH隊列,當一個線程執行完畢(lock.unlock())時會啟用自己的後繼節點,但正在執行的線程並不在隊列中,而那些等待執行的線程全部處於阻塞狀態,經過調查線程的顯式阻塞是通過調用LockSupport.park()完成,而LockSupport.park()則調用sun.misc.Unsafe.park()本地方法,再進一步,HotSpot在Linux中中通過調用pthread_mutex_lock函數把線程交給系統核心進行阻塞。
該隊列如圖:
與synchronized相同的是,這也是一個虛擬隊列,不存在隊列執行個體,僅存在節點之間的前後關係。令人疑惑的是為什麼採用CLH隊列呢。原生的CLH隊列是用於自旋鎖,但Doug Lea把其改造為阻塞鎖。
當有線程競爭鎖時,該線程會首先嘗試獲得鎖,這對於那些已經在隊列中排隊的線程來說顯得不公平,這也是非公平鎖的由來,與synchronized實作類別似,這樣會極大提高輸送量。
如果已經存在Running線程,則新的競爭線程會被追加到隊尾,具體是採用基於CAS的Lock-Free演算法,因為線程並發對Tail調用CAS可能會導致其他線程CAS失敗,解決辦法是迴圈CAS直至成功。AbstractQueuedSynchronizer的實現非常精巧,令人歎為觀止,不入細節難以完全領會其精髓,下面詳細說明實現過程:
2.1 NonFairSync.lock
Java代碼 /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { // 如果鎖沒有被任何線程鎖定且加鎖成功則設定當前線程為鎖的擁有者 // 如果鎖已被當前線程鎖定,則在acquire中將狀態加1並返回 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else // 加鎖失敗,再次嘗試加鎖,失敗則加入等待隊列,禁用當前線程,直到被中斷或有線程釋放鎖時被喚醒 acquire(1); }
2.2 Sync.nonfairTryAcquire
nonfairTryAcquire方法將是lock方法間接調用的第一個方法,每次請求鎖時都會首先調用該方法。 Java代碼 /** * Performs non-fair tryLock. tryAcquire is * implemented in subclasses, but both need nonfair * try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 如果鎖空閑則嘗試鎖定,成功則設當前線程為鎖擁有者 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 若當前線程為鎖擁有者則直接修改鎖狀態計數 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } // 嘗試擷取失敗,返回 return false; }
該方法會首先判斷目前狀態,如果c==0說明沒有線程正在競爭該鎖,如果不c !=0 說明有線程正擁有了該鎖。
1.如果發現c==0,則通過CAS設定該狀態值為acquires,acquires的初始調用值為1,如果CAS設定成功,則可以預計其他任何線程調用CAS都不會再成功,也就認為當前線程得到了該鎖,也作為Running線程,很顯然這個Running線程並未進入等待隊列。
2.如果c !=0 但發現自己已經擁有鎖,只是簡單地重新計算 status + acquires,並修改status值,但因為沒有競爭,所以通過setStatus修改,而非CAS,也就是說這段代碼實現了偏向鎖的功能,並且實現的非常漂亮。每次線程重入該鎖都會+1,每次unlock都會-1,但為0時釋放鎖。 2.3 AbstractQueuedSynchronizer.addWaiter
addWaiter方法負責把當前無法獲得鎖的線程封裝為一個Node添加到隊尾: Java代碼