源碼解讀系列之: ReentrantLock

ReentrantLock是java1.5中lock框架中Lock接口的一個實現類.一個ReentrantLock的實例鎖擁有和synchronized相同的行為和語義,并且還擴展了其他能力.
ReentrantLock是通過一種什么機制來實現鎖的呢?也就是說ReentrantLock到底是鎖住了什么東西來控制線程同步的呢?下面將詳細講解.
ReentrantLock內部實現了一個AbstractQueuedSynchronizer的同步器,ReentrantLock就是通過控制這個AbstractQueuedSynchronizer同步器來實現多個線程之間的同步.那么具體的實現原理是怎么樣的呢?
AbstractQueuedSynchronizer類會維護一個等待隊列,并且擁有一個state(volatile修飾)的成員變量屬性,AbstractQueuedSynchronizer就是通過這個state來控制多個線程的同步訪問的.當ReentrantLock調用lock()方法時,內部實際調用的是Sync(AbstractQueuedSynchronizer的實現類)的lock方法,此方法會先判斷AbstractQueuedSynchronizer中state屬性的值:如果state!=0,說明當前有線程在占用鎖資源,然后接著判斷當前占用該鎖資源的線程是否跟當前線程是同一個線程,如果是同一個線程那么state值就加一,同時當前線程繼續執行,如果不是同一個線程,那么就會把當前線程加入等待隊列(并且會一直嘗試獲取鎖資源).如果state==0,說明當前鎖資源是可用,那么直接設置當前線程占用了鎖資源并返回繼續執行.執行完成之后,必須要手動釋放鎖資源.


下面對ReentrantLock的源碼并相關類源碼進行解讀:

類定義

public class ReentrantLock implements Lock, java.io.Serializable

成員變量

修飾符 變量名 作用
private final Sync sync 實際同步鎖控制實例

構造方法

無參構造方法
無參構造方法會給sync賦值,默認賦值為不公平鎖同步(公平鎖,不公平鎖下面會講)

public ReentrantLock() {
    sync = new NonfairSync();
}

有參構造方法
參數:
true 公平鎖,線程獲取鎖的順序跟等待的時間有關,先等待的線程先獲取鎖
false 不公平鎖,線程獲取鎖是隨機的

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

核心方法講解

lock()方法

public void lock() {
    // 調用Sync(Sync是AbstractQueuedSynchronizer的子類)的lock()方法
    // Sync是ReentrantLock的內部類,同時在ReentrantLock中有兩種實現方式
    // 1. FairSync(公平實現)
    // 2. NonFairSync(不公平實現)
    sync.lock();
}

-- ------------------------------------------------------ --
-- ---------------FairSync獲取鎖的實現-------------------- --
-- ------------------------------------------------------ --
/**
* FairSync的lock方法實現
**/
final void lock() {
    // acquire方法是AbstractQueuedSynchronizer中的方法
    // 此方法會調用tryAcquire(int)方法來試圖獲取鎖資源
    acquire(1);
}

/**
* AbstractQueuedSynchronizer中acquire()方法的實現
**/
public final void acquire(int arg) {
    // 試圖獲取鎖資源(會調用對應實現的tryAcquire()方法),
    // 不成功則加入等待隊列并獲取隊列(addWaiter()會將當前線程加入到等待隊列尾部)
    // acquireQueued()(不知道怎么說,等會直接講代碼)
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

/**
* FairSync中tryAcquire()的實現
* 返回
*   true: 獲取鎖成功
*   false: 獲取鎖不成功
**/
protected final boolean tryAcquire(int acquires) {
    // 獲取當前線程
    final Thread current = Thread.currentThread();
    // 獲取鎖資源的狀態
    // 0: 說明當前鎖可立即獲取,在此種狀態下(又是公平鎖)
    // >0并且當前線程與持有鎖資源的線程是同一個線程則state + 1并返回true
    // >0并且占有鎖資源的不是當前線程,則返回false表示獲取不成功
    int c = getState();
    if (c == 0) {
        // 在鎖可以立即獲取的情況下
        // 首先判斷線程是否是剛剛釋放鎖資源的頭節點的下一個節點(線程的等待先后順序)
        // 如果是等待時間最長的才會馬上獲取到鎖資源,否則不會(這也是公平與不公平的主要區別所在)
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {  //線程可以遞歸獲取鎖
        int nextc = c + acquires;
        // 超過int上限值拋出錯誤
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

/**
* AbstractQueuedSynchronizer中acquireQueued()方法講解
**/
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 注意這里的無限循環(表現出來就是等待的一個過程)
        for (;;) {
            // 獲得前一個非null節點
            final Node p = node.predecessor();
            // 如果前任節點是頭結點(頭節點是表示獲取到了資源鎖的節點)
            // 并且試圖獲取鎖資源(只有在p節點release()掉鎖資源才會獲取成功)
            // 如果獲取鎖資源成功,就將此線程節點設置為頭結點,并把p節點的引用斷開
            // 獲取鎖成功就退出循環
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            
            // 如果獲取資源失敗了,那就先判斷線程是否需要被阻塞,需要就阻塞線程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
                
            // 繼續循環等待
        }
    } finally {
        // 如果獲取鎖資源失敗了,
        // 那么就把線程的等待狀態設置為cancelled(此狀態的線程不會再獲得鎖資源)
        if (failed)
            cancelAcquire(node);
    }
}


-- ------------------------------------------------------ --
-- ---------------NonFairSync獲取鎖的實現----------------- --
-- ------------------------------------------------------ --
/**
* lock()方法實現
**/
final void lock() {
    // 如果鎖空閑則立即獲取鎖資源,設置鎖資源的當前占用線程
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // acquire()過程跟FairSync是一樣的,
        // 主要區別在于NonFairSync的tryAcqure()有不同的實現
        acquire(1);
}

/**
* tryAcquire()方法實現
* 調用的nonfairTryAcquire()方法,這是父類Sync中的方法實現
**/
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

/**
* Sync中的nonfairTryAcquire()方法實現
* 這個跟公平類中的實現主要區別在于不會判斷當前線程是否是等待時間最長的線程
**/ 
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 跟FairSync中的主要區別,不會判斷hasQueuedPredecessors()
        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;
}

unlock()方法
釋放線程占用的鎖

public void unlock() {
    // 調用Sync中的release()方法,所以公平和不公平類釋放鎖是一樣的
    // 實際調用AbstractQueuedSynchronizer中的release()
    sync.release(1);
}

/**
* AbstractQueuedSynchronizer中的release()
**/
public final boolean release(int arg) {
    // 釋放鎖資源,子類會重寫這個方法(在這里就是調用的Sync中tryRelease())
    if (tryRelease(arg)) {
        // 釋放鎖資源成功后,會執行一個喚醒后續線程(喚醒一個)的操作
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

/**
* Sync中tryRelease()
**/
protected final boolean tryRelease(int releases) {
    // 修改當前鎖的狀態
    // 如果一個線程遞歸獲取了該鎖(也就是state != 1), 那么c可能不等0
    // 如果沒有線程遞歸獲取該鎖,則c == 0
    int c = getState() - releases;
    
    // 如果鎖的占有線程不等于當前正在執行釋放操作的線程,則拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // c == 0,表示當前線程釋放鎖成功,同時表示遞歸獲取了該鎖的線程已經執行完畢
    // 則設置當前鎖狀態為free,同時設置鎖的當前線程為null,可以讓其他線程來獲取
    // 同時也說明,如果c != 0,則表示線程遞歸占用了鎖資源,
    // 所以鎖的當前占用線程依然是當前釋放鎖的線程(實際沒有釋放)
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 重新設置鎖的占有數
    setState(c);
    return free;
}

條件鎖的操作

public Condition newCondition() {
    return sync.newCondition();
}

這里不對源碼進行解讀,只對原理進行一個說明,上述函數實際上是執行了一個new ConditionObject();的操作,  
這個類是同步器中的一個內部類,這個類內部是維護了一個條件隊列,條件鎖的操作也是針對這個條件隊列的一個操作.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容