ReentrantLock原始碼解析

歸來少年之時發表於2019-03-08

class

  • abstract static class Sync extends AbstractQueuedSynchronizer
  • static final class NonfairSync extends Sync
  • static final class FairSync extends Sync

程式碼使用示例

private ReentrantLock reentrantLock = new ReentrantLock();
    public void  test (){
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName()+ "test....begin");
        try {
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"test....end");
        reentrantLock.unlock();
    }
複製程式碼
ReentrantLock 提供兩種方式加鎖 公平和非公平
預設為非公平 如果要使用非公平建構函式傳true
加公平鎖呼叫FairSync.lock()
加非公平鎖呼叫NonfairSync.lock()
釋放鎖呼叫AbstractQueuedSynchronizer.release(int arg)

程式碼解析

NonfairSync.lock 程式碼

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
複製程式碼
  1. 先呼叫AbstractQueuedSynchronizer.compareAndSetState(int expect, int update) AQS通過CAS維護了state欄位,0代表沒有被執行緒佔有 所以非公平鎖先嚐試通過CAS替換state,如果替換成功代表沒有被別的執行緒佔有, 設定當前執行緒佔有,lock方法結束,執行緒可以繼續往下走了
  2. 如果沒有替換成功再呼叫AbstractQueuedSynchronizer.acquire(int arg)

AbstractQueuedSynchronizer.acquire(int arg)程式碼

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
複製程式碼
  1. tryAcquire(arg) 嘗試獲取鎖 NonfairSync.tryAcquire(int acquires)--> Sync.nonfairTryAcquire(int acquires) 程式碼片段
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState(); 獲取state欄位的值
            if (c == 0) { 如果值是0 代表沒有執行緒佔用
                if (compareAndSetState(0, acquires)) { 通過CAS替換看自己是否可以佔用成功
                    setExclusiveOwnerThread(current);
                    return true; 如果成功就返回true
                }
            }
            如果有執行緒佔用,判斷佔用的執行緒和自己是否是同一個執行緒
            else if (current == getExclusiveOwnerThread()) {
                //如果是同一個執行緒 state欄位加上傳入的引數
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //更新state
                setState(nextc);
                //從這可以看出 NonfairSync.lock是重入鎖
                return true;
            }
            //如果都沒滿足,代表加鎖不成功
            return false;
        }    
複製程式碼
  1. 如果tryAcquire(arg)返回ture,代表加鎖成功,後面就不用走了 如果加鎖不成功,繼續acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
  2. 先看addWaiter(Node.EXCLUSIVE) 呼叫的是AbstractQueuedSynchronizer.addWaiter AbstractQueuedSynchronizer.addWaiter程式碼片段
private Node addWaiter(Node mode) {
        //把自己當前執行緒封裝成一個node
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //判斷隊尾是不是空
        if (pred != null) {
            //如果隊尾不是空
            node.prev = pred;
            //就把自己node的上一個結點設定為隊尾那個結點
            更新自己結點為隊尾結點
            if (compareAndSetTail(pred, node)) {
                //如果更新成功,把上一個結點的下一個結點設定為自己結點
                pred.next = node;
                //返回當前結點
                return node;
            }
        }
        
        
        enq(node);
        return node;
    }
複製程式碼

如果隊尾為空或者更新自己為隊尾結點失敗繼續enq(node) AbstractQueuedSynchronizer.enq片段

private Node enq(final Node node) {
    無限迴圈CAS操作,直到把自己變成隊尾結點
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
複製程式碼

總結:addWaiter(Node.EXCLUSIVE) 是組裝當前執行緒結點,然後通過CAS新增到隊尾

  1. acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 通過addWaiter(Node.EXCLUSIVE)已經把自己加入佇列了 那acquireQueued這個方法是做什麼呢

AbstractQueuedSynchronizer.acquireQueued程式碼片段

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //獲得當前結點的前一個結點
                final Node p = node.predecessor();
                //如果head是前一個結點,我就再次嘗試獲取鎖,萬一獲取成功了呢
                if (p == head && tryAcquire(arg)) {
                    //如果加鎖成功就把自己設定為頭結點
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    //獲取成功 就代表獲得鎖
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
複製程式碼

如果還沒有獲得的鎖就走shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt() 先看shouldParkAfterFailedAcquire(p, node) AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire程式碼片段

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //入參是前一個結點和當前結點
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //如果前一個是-1,代表可以安心的等待
            return true;
        if (ws > 0) {
            //如果前一個大於0,代表前一個等待超時或者被中斷了,需要從同步佇列中取消該Node的結點,所以繼續找在等待的前結點,把找到的結點的下一個結點設為當前結點
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //如果是小於0的其他狀態都統一設為-1,等待狀態
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
複製程式碼

這個方法其實就是如果前驅結點的狀態不是SIGNAL,那麼自己就不能安心去休息,需要去找個安心的休息點,然後自己設為這個前驅結點的後一個結點

附上node結點的waitStatus含義:

  • CANCELLED:值為1,在同步佇列中等待的執行緒等待超時或被中斷,需要從同步佇列中取消該Node的結點,其結點的waitStatus為CANCELLED,即結束狀態,進入該狀態後的結點將不會再變化。
  • SIGNAL:值為-1,被標識為該等待喚醒狀態的後繼結點,當其前繼結點的執行緒釋放了同步鎖或被取消,將會通知該後繼結點的執行緒執行。說白了,就是處於喚醒狀態,只要前繼結點釋放鎖,就會通知標識為SIGNAL狀態的後繼結點的執行緒執行。
  • CONDITION:值為-2,與Condition相關,該標識的結點處於等待佇列中,結點的執行緒等待在Condition上,當其他執行緒呼叫了Condition的signal()方法後,CONDITION狀態的結點將從等待佇列轉移到同步佇列中,等待獲取同步鎖。
  • PROPAGATE:值為-3,與共享模式相關,在共享模式中,該狀態標識結點的執行緒處於可執行狀態。
  • 0狀態:值為0,代表初始狀態

如果找到安全休息點後就繼續parkAndCheckInterrupt方法 真正讓執行緒等待的方法 AbstractQueuedSynchronizer.parkAndCheckInterrupt程式碼片段

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//呼叫park()使執行緒進入waiting狀態
        return Thread.interrupted();//如果被喚醒,檢視自己是不是被中斷的。
    }
複製程式碼

park()會讓當前執行緒進入waiting狀態。在此狀態下,有兩種途徑可以喚醒該執行緒:1)被unpark();2)被interrupt()。需要注意的是,Thread.interrupted()會清除當前執行緒的中斷標記位

前面已經介紹了NonfairSync.lock(),再看下FairSync.lock() FairSync.lock()程式碼片段

final void lock() {
            acquire(1);
        }
複製程式碼

相比於NonfairSync.lock(),少了一個CAS判斷,因為是公平鎖,所以不像非公平鎖那樣直接上來你嘗試自己能不能獲得鎖 後面呼叫邏輯只是在FairSync.tryAcquire有區別,其他都一樣

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
複製程式碼

比較Sync.nonfairTryAcquire(int acquires)只是多了!hasQueuedPredecessors()這個判斷 就是在嘗試獲取鎖的時候,不是直接去獲取,而是看佇列中有沒有其他等待的執行緒,如果有,自己是不能直接去獲取鎖的

小結:ReentrantLock的公平鎖和非公平鎖加鎖就講完了,公平和非公平體現在 公平獲取鎖時是有先來後到的,非公平在嘗試獲取鎖時,是不管佇列中是否有其他執行緒在等待,自己直接去CAS嘗試加鎖,如果不成功才放入佇列

ReentrantLock.unlock()其實呼叫的是AbstractQueuedSynchronizer.release(1) AbstractQueuedSynchronizer.release程式碼片段

public final boolean release(int arg) {
        if (tryRelease(arg)) {//釋放可以釋放鎖資源
            Node h = head; //找到佇列頭
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//根據頭結點喚醒下一個執行緒
            return true;
        }
        return false;
    }
複製程式碼

tryRelease呼叫的是ReentrantLock.tryRelease 程式碼片段

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;//計算state狀態量
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//如果是0代表可以釋放
                free = true;
                setExclusiveOwnerThread(null);//清空鎖資源佔有的執行緒
            }
            setState(c);設定變數
            return free;
        }
複製程式碼

這個是重入鎖釋放的設計,因為在獲取鎖時,同一個執行緒可以多次對同一個資源加鎖的,每加一次的時候state都會加1,釋放也一樣都會減一,只有當state等於0的時候才代表這個執行緒釋放完了,所以在寫ReentrantLock時,lock和unlock要成對出現,否則會一直在那佔有資源

接下來看AbstractQueuedSynchronizer.unparkSuccessor程式碼片段

private void unparkSuccessor(Node node) {
        //這裡,node一般為當前執行緒所在的結點
        int ws = node.waitStatus;
        if (ws < 0) //置零當前執行緒所在的結點狀態,允許失敗。
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next; //找到下一個需要喚醒的結點s
        if (s == null || s.waitStatus > 0) {//如果為空或已取消
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0) //從這裡可以看出,<=0的結點,都是還有效的結點
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread); //喚醒
    }
複製程式碼

最終就是找到下一個有效的結點,然後喚醒該結點對應的執行緒

相關文章