基於jdk8分析ReentranLock源碼

 通過下述代碼,來深挖ReentranLock的底層實現。下述代碼使用了ReentrantLock鎖的lock、unlock、condition等常用方法。讓我們一一解析底層實現。

public class TestReentrantLock {
    private static final int MAX_CACHE_SIZE = 16; 
    private Map<String, String> cache = new HashMap<>();
    private ReentrantLock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();
    
    public void put(String key, String value) {
        lock.lock();
        try {
            while (cache.size() == MAX_CACHE_SIZE) {
                notFull.await();
            }
            cache.put(key, value);
            notFull.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    
    public void remove(String key) {
        lock.lock();
        try {
            while (cache.isEmpty()) {
                notEmpty.await();
            }
            cache.remove(key);
            notFull.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

lock.lock()就是申請鎖,底層調用代碼如下:

public void lock() {
        sync.lock();
    }

其中,sync是ReentrantLock對象中的內部類abstract static class Sync extends AbstractQueuedSynchronizer。能夠看到Sync是一個抽象類,它有兩個子類FairSync(公平鎖)和NonFailSync(非公平鎖)。兩個都實現了lock()方法

final void lock() {
            acquire(1);
        }

而acquire(1),1表示申請的鎖資源數量,而acquire方法是繼承於AbstractQueuedSynchronizer(AQS),如下:

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

獲取鎖:

       首先會嘗試獲取鎖,

               獲取失敗時將Thread封成Node節點放入雙向隊列中

                     1如果他的前置節點爲head節點,則再次嘗試獲取鎖

                     2如果他的前置節點部位head節點,則設置前置節點狀態爲SIGNAL(表示需要前置節點喚醒),設置成功則阻塞,否則,循環處理12

                    當上述12異常退出時,若node爲head節點,喚醒後繼節點

大概寫寫,後續補個詳細的流程圖吧~~~

下述爲具體的源碼細節。

acquire(int arg)就是由AQS提供的獲取鎖的框架,其中tryAcquire(arg)就是由子類具體實現獲取鎖的邏輯。


    protected final boolean tryAcquire(int acquires) {
    //獲取當前線程
        final Thread current = Thread.currentThread();
    //獲取int state的值,state在不同鎖中代表的含義不一樣。共享鎖中代表共享變量的數量,在獨佔鎖中表示鎖是否被獨佔以及重入次數;
    //ReentranLock重入鎖中,等於0時,表示鎖沒有被獨佔;大於等於1時,表示被獨佔且重入的次數。
        int c = getState();
        if (c == 0) {
            //hasQueuedPredecessors()表示AQS隊列中是否存在其他阻塞線程,存在時,獲取鎖失敗。此處也是公平鎖和非公平鎖的主要差別。
            // 如果隊列中不存在阻塞線程,通過cas嘗試設置state值爲acuires,同時,通過setExclusiveOwnerThread(thread)將該線程設置爲鎖的獨佔線程
            if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 通過getExclusiveOwnerThread()判斷嘗試獲取鎖的線程,與已經獲取鎖的線程是否爲同一個。鎖的重入機制
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            //設置state的值爲鎖的最新重入次數。當重入次數減爲0時,才表示鎖沒有被任何線程獨佔
            setState(nextc);
            return true;
        }
        return false;
    }

    private Node addWaiter(Node mode) {
        //構造線程節點
        Node node = new Node(Thread.currentThread(), mode);
        //AQS中的線程阻塞隊列是雙向鏈表支持的,head、tail分別表示頭尾節點
        Node pred = tail;
        //尾節點不爲null時,嘗試向尾節點處添加node節點
        if (pred != null) {
            //node節點的前置指針指到尾節點上,然後嘗試,將尾節點設爲node節點
            //假設線程a/b都執行到這一步時,會同時將自己的node節點的前置指針指向tail,但是cas保證只會有一個被設置爲尾節點
            //如果將這一步放到if語句中,因爲同時有線程釋放鎖且喚醒阻塞線程的情況,因此,會出現阻塞隊列短暫的斷鏈。可以和釋放鎖時的邏輯相互印證
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                //將node前置節點的next指針指向node節點,添加成功
                pred.next = node;
                return node;
            }
        }
        //加入尾節點爲null時,直接入隊
        enq(node);
        return node;
    }

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //加入尾節點爲null時,表示阻塞隊列爲空,通過cas初始化dummy節點
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //重新向尾節點後添加node節點
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    //返回node節點的前置節點。因爲存在一個dummy頭,因此,加入阻塞隊列只有一個隊列時,t就是head頭節點
                    return t;
                }
            }
        }
    }
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //for循環一直循環嘗試獲取鎖,當不是head頭時,會park
            for (;;) {
                //獲取node節點的前置節點,當前置節點爲head節點時,表示阻塞隊列中只有自身
                // 然後tryAcquire()嘗試獲取鎖,成功後,將該node節點出隊,返回中斷標誌位位false
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //當獲取鎖失敗時,阻塞該線程。並且檢查線程的中斷狀態,但其實並沒有響應中斷.
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            //當上述流程正常退出時,failed=false,說明線程獲取到鎖了,會在release時候喚醒某個線程;
            //當異常退出時,需要將該節點置爲CANCELLED狀態,且當是第一個node節點時,喚醒下一個節點
            if (failed)
                cancelAcquire(node);
        }
    }

 


    //pred表示node節點的前置節點
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //waitStatus是將線程封裝的Node節點的狀態,包括CANCELLED(1)/SINGAL(-1)/CONDITION(-2)/PROPAGATE(-3)
        //CANCELLED表示該線程因爲超時或中斷而取消獲取鎖,該線程就沒有被阻塞,不需要喚醒等操作
        //SINGAL表示節點的後繼節點被阻塞,需要喚醒
        //CONDITION表示節點在CONDITION的隊列中,不會出現在AQS的雙端隊列中,當條件滿足時,waitStatus會修改爲0,進入雙端隊列
        //PROPAGATE表示傳播,在共享鎖中使用,具體不知道
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //當node節點的前置節點爲SIGNAL時,表示它可以安全的park(LockSupport提供的park和unpark用於阻塞喚醒線程,底層由unsafe支持)
            return true;
        //ws>0表示node前置節點是取消狀態
        if (ws > 0) {
            //do-while用於獲取node前的第一個不是CANCELLED狀態的節點
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //嘗試將前置節點狀態設爲SIGNAL,注意:雙端隊列中是沒有CONDITION狀態的node節點的,CONDITION狀態的node節點會轉爲0進入雙端隊列
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
private final boolean parkAndCheckInterrupt() {
        //使用LockSupport的park阻塞線程
        LockSupport.park(this);
        //獲取當前線程的中斷狀態,如果被中斷返回true,否則爲false
        return Thread.interrupted();
    }
    private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;
        node.thread = null;
        //找到不是CANCELLED狀態的前置線程
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;
        Node predNext = pred.next;
        //設置node節點爲CANCELLED狀態
        node.waitStatus = Node.CANCELLED;
        //如果node爲尾節點,則將找到的第一個不爲CANCELLED狀態的前置節點設爲尾節點
        if (node == tail && compareAndSetTail(node, pred)) {
            //設置pred的next節點爲null,將CANCELLED的節點全部和雙端隊列斷了,gc
            compareAndSetNext(pred, predNext, null);
        } else {
            //如果node不是尾節點。pred爲head說明他是雙端隊列的第一個節點,所以喚醒後繼節點
            //如果pred不爲head節點,說明,node節點前後各有線程阻塞在雙端隊列中
            int ws;
            if (pred != head &&
                    ((ws = pred.waitStatus) == Node.SIGNAL ||
                            (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                    pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }

釋放鎖:

嘗試釋放鎖。釋放成功則喚醒head的後繼節點。釋放失敗的場景是:可重入鎖多次獲取鎖,但只釋放其中一次或幾次等

public void unlock() {
        sync.release(1);
    }
//由AQS提供的釋放鎖框架
public final boolean release(int arg) {
        //嘗試釋放鎖,成功則喚醒第一個節點
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
protected final boolean tryRelease(int releases) {
            //獲取state值,即重入的次數,減釋放鎖的次數
            int c = getState() - releases;
            //如果當前獨佔鎖的線程不是自己,則拋出異常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //當c==0表示獨佔線程將自身所有的鎖都釋放了
            if (c == 0) {
                free = true;
                //將線程獨佔標記爲null
                setExclusiveOwnerThread(null);
            }
            //更新最新的state值
            setState(c);
            return free;
        }

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章