【JUC】JDK1.8原始碼分析之ReentrantLock(三)

leesf發表於2016-04-13

一、前言

  在分析了AbstractQueuedSynchronier原始碼後,接著分析ReentrantLock原始碼,其實在AbstractQueuedSynchronizer的分析中,已經提到過ReentrantLock,ReentrantLock表示下面具體分析ReentrantLock原始碼。

二、ReentrantLock資料結構

  ReentrantLock的底層是藉助AbstractQueuedSynchronizer實現,所以其資料結構依附於AbstractQueuedSynchronizer的資料結構,關於AQS的資料結構,在前一篇已經介紹過,不再累贅。

三、ReentrantLock原始碼分析

  3.1 類的繼承關係 

public class ReentrantLock implements Lock, java.io.Serializable

  說明:ReentrantLock實現了Lock介面,Lock介面中定義了lock與unlock相關操作,並且還存在newCondition方法,表示生成一個條件。

  3.2 類的內部類

  ReentrantLock總共有三個內部類,並且三個內部類是緊密相關的,下面先看三個類的關係。

  說明:ReentrantLock類內部總共存在Sync、NonfairSync、FairSync三個類,NonfairSync與FairSync類繼承自Sync類,Sync類繼承自AbstractQueuedSynchronizer抽象類。下面逐個進行分析。

  1. Sync類

  Sync類的原始碼如下  

abstract static class Sync extends AbstractQueuedSynchronizer {
        // 序列號
        private static final long serialVersionUID = -5179523762034025860L;
        
        // 獲取鎖
        abstract void lock();
        
        // 非公平方式獲取
        final boolean nonfairTryAcquire(int acquires) {
            // 當前執行緒
            final Thread current = Thread.currentThread();
            // 獲取狀態
            int c = getState();
            if (c == 0) { // 表示沒有執行緒正在競爭該鎖
                if (compareAndSetState(0, acquires)) { // 比較並設定狀態成功,狀態0表示鎖沒有被佔用
                    // 設定當前執行緒獨佔
                    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;
        }
        
        // 試圖在共享模式下獲取物件狀態,此方法應該查詢是否允許它在共享模式下獲取物件狀態,如果允許,則獲取它
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread()) // 當前執行緒不為獨佔執行緒
                throw new IllegalMonitorStateException(); // 丟擲異常
            // 釋放標識
            boolean free = false; 
            if (c == 0) {
                free = true;
                // 已經釋放,清空獨佔
                setExclusiveOwnerThread(null); 
            }
            // 設定標識
            setState(c); 
            return free; 
        }
        
        // 判斷資源是否被當前執行緒佔有
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        // 新生一個條件
        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class
        // 返回資源的佔用執行緒
        final Thread getOwner() {        
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        // 返回狀態
        final int getHoldCount() {            
            return isHeldExclusively() ? getState() : 0;
        }

        // 資源是否被佔用
        final boolean isLocked() {        
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        // 自定義反序列化邏輯
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }
View Code

  說明:Sync類存在如下方法和作用如下。

  2. NonfairSync類

  NonfairSync類繼承了Sync類,表示採用非公平策略獲取鎖,其實現了Sync類中抽象的lock方法,原始碼如下。

// 非公平鎖
    static final class NonfairSync extends Sync {
        // 版本號
        private static final long serialVersionUID = 7316153563782823691L;

        // 獲得鎖
        final void lock() {
            if (compareAndSetState(0, 1)) // 比較並設定狀態成功,狀態0表示鎖沒有被佔用
                // 把當前執行緒設定獨佔了鎖
                setExclusiveOwnerThread(Thread.currentThread());
            else // 鎖已經被佔用,或者set失敗
                // 以獨佔模式獲取物件,忽略中斷
                acquire(1); 
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
View Code

  說明:從lock方法的原始碼可知,每一次都嘗試獲取鎖,而並不會按照公平等待的原則進行等待,讓等待時間最久的執行緒獲得鎖。

  3. FairSyn類

  FairSync類也繼承了Sync類,表示採用公平策略獲取鎖,其實現了Sync類中的抽象lock方法,原始碼如下。 

// 公平鎖
    static final class FairSync extends Sync {
        // 版本序列化
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            // 以獨佔模式獲取物件,忽略中斷
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        // 嘗試公平獲取鎖
        protected final boolean tryAcquire(int acquires) {
            // 獲取當前執行緒
            final Thread current = Thread.currentThread();
            // 獲取狀態
            int c = getState();
            if (c == 0) { // 狀態為0
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) { // 不存在已經等待更久的執行緒並且比較並且設定狀態成功
                    // 設定當前執行緒獨佔
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { // 狀態不為0,即資源已經被執行緒佔據
                // 下一個狀態
                int nextc = c + acquires;
                if (nextc < 0) // 超過了int的表示範圍
                    throw new Error("Maximum lock count exceeded");
                // 設定狀態
                setState(nextc);
                return true;
            }
            return false;
        }
    }
View Code

  說明:跟蹤lock方法的原始碼可知,當資源空閒時,它總是會先判斷sync佇列(AbstractQueuedSynchronizer中的資料結構)是否有等待時間更長的執行緒,如果存在,則將該執行緒加入到等待佇列的尾部,實現了公平獲取原則。其中,FairSync類的lock的方法呼叫如下,只給出了主要的方法。

  說明:可以看出只要資源被其他執行緒佔用,該執行緒就會新增到sync queue中的尾部,而不會先嚐試獲取資源。這也是和Nonfair最大的區別,Nonfair每一次都會嘗試去獲取資源,如果此時該資源恰好被釋放,則會被當前執行緒獲取,這就造成了不公平的現象,當獲取不成功,再加入佇列尾部。

  3.3 類的屬性

public class ReentrantLock implements Lock, java.io.Serializable {
    // 序列號
    private static final long serialVersionUID = 7373984872572414699L;    
    // 同步佇列
    private final Sync sync;
}

  說明:ReentrantLock類的sync非常重要,對ReentrantLock類的操作大部分都直接轉化為對Sync和AbstractQueuedSynchronizer類的操作。

  3.4 類的建構函式

  1. ReentrantLock()型建構函式  

public ReentrantLock() {
        // 預設非公平策略
        sync = new NonfairSync();
    }
View Code

  說明:可以看到預設是採用的非公平策略獲取鎖。

  2. ReentrantLock(boolean)型建構函式

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

  說明:可以傳遞引數確定採用公平策略或者是非公平策略,引數為true表示公平策略,否則,採用非公平策略。

  3.5 核心函式分析

  通過分析ReentrantLock的原始碼,可知對其操作都轉化為對Sync物件的操作,由於Sync繼承了AQS,所以基本上都可以轉化為對AQS的操作。如將ReentrantLock的lock函式轉化為對Sync的lock函式的呼叫,而具體會根據採用的策略(如公平策略或者非公平策略)的不同而呼叫到Sync的不同子類。

  所以可知,在ReentrantLock的背後,是AQS對其服務提供了支援,由於之前我們分析AQS的核心原始碼,遂不再累贅。下面還是通過例子來更進一步分析原始碼。

四、示例分析

  4.1 公平鎖 

package com.hust.grid.leesf.abstractqueuedsynchronizer;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyThread extends Thread {
    private Lock lock;
    public MyThread(String name, Lock lock) {
        super(name);
        this.lock = lock;
    }
    
    public void run () {
        lock.lock();
        try {
            System.out.println(Thread.currentThread() + " running");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            lock.unlock();
        }
    }
}

public class AbstractQueuedSynchonizerDemo {
    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock(true);
        
        MyThread t1 = new MyThread("t1", lock);        
        MyThread t2 = new MyThread("t2", lock);
        MyThread t3 = new MyThread("t3", lock);
        t1.start();
        t2.start();    
        t3.start();
    }
}

  執行結果(某一次): 

Thread[t1,5,main] running
Thread[t2,5,main] running
Thread[t3,5,main] running

  說明:該示例使用的是公平策略,由結果可知,可能會存在如下一種時序。

  說明:首先,t1執行緒的lock操作 -> t2執行緒的lock操作 -> t3執行緒的lock操作 -> t1執行緒的unlock操作 -> t2執行緒的unlock操作 -> t3執行緒的unlock操作。根據這個時序圖來進一步分析原始碼的工作流程。

  ① t1執行緒執行lock.lock,下圖給出了方法呼叫中的主要方法。

  說明:由呼叫流程可知,t1執行緒成功獲取了資源,可以繼續執行。

  ② t2執行緒執行lock.lock,下圖給出了方法呼叫中的主要方法。

  

  說明:由上圖可知,最後的結果是t2執行緒會被禁止,因為呼叫了LockSupport.park。

  ③ t3執行緒執行lock.lock,下圖給出了方法呼叫中的主要方法。

  說明:由上圖可知,最後的結果是t3執行緒會被禁止,因為呼叫了LockSupport.park。

  ④ t1執行緒呼叫了lock.unlock,下圖給出了方法呼叫中的主要方法。

  說明:如上圖所示,最後,head的狀態會變為0,t2執行緒會被unpark,即t2執行緒可以繼續執行。此時t3執行緒還是被禁止。

  ⑤ t2獲得cpu資源,繼續執行,由於t2之前被park了,現在需要恢復之前的狀態,下圖給出了方法呼叫中的主要方法。

  說明:在setHead函式中會將head設定為之前head的下一個結點,並且將pre域與thread域都設定為null,在acquireQueued返回之前,sync queue就只有兩個結點了。

  ⑥ t2執行lock.unlock,下圖給出了方法呼叫中的主要方法。

  說明:由上圖可知,最終unpark t3執行緒,讓t3執行緒可以繼續執行。

  ⑦ t3執行緒獲取cpu資源,恢復之前的狀態,繼續執行。

  說明:最終達到的狀態是sync queue中只剩下了一個結點,並且該節點除了狀態為0外,其餘均為null。

  ⑧ t3執行lock.unlock,下圖給出了方法呼叫中的主要方法。

  說明:最後的狀態和之前的狀態是一樣的,佇列中有一個空節點,頭結點為尾節點均指向它。

  使用公平策略和Condition的情況可以參考上一篇關於AQS的原始碼示例分析部分,不再累贅。

五、總結

  再掌握了AQS後,再來分析ReentrantLock的原始碼,就會非常簡單,因為ReentrantLock的絕大部分操作都是基於AQS類的。所以,進行分析時要找準方向,就會事半功倍。謝謝各位園友觀看~

相關文章