ReentrantLock源碼解讀

前言

寫這篇文章以前,仍是先安利一本書:《java併發編程的藝術》。這本書對鎖的實現的不少細節都解釋的仍是很清楚的,加上本身配合源碼進行理解,讀懂ReentrantLock這個類的實現應該不是那麼困難。本文只對獨佔模式進行分析。java


一行行分析ReentrantLock源碼

直接步入正題,先貼一段代碼看看如何使用ReentrantLock:node

public class ReentrantLockTest {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock(true);  //1
        lock.lock();  //2
        try {
            //do something
        } finally {
            lock.unlock(); //3
        }
    }
}
ReentrantLock的構造

上面代碼的步驟1是調用ReentrantLock構造方法進行初始化,這裏ReentrantLock給咱們提供了兩種鎖的實現,一個是公平鎖,一個是非公平鎖。這兩種鎖顧名思義,一個排隊幹活,一個搶着幹~~編程

//默認構造函數,獲得的是非公平鎖的實現
 public ReentrantLock() {
       sync = new NonfairSync();
 }
//傳入true獲得公平鎖的實現,傳入false則獲得公平鎖的實現
 public ReentrantLock(boolean fair) {
       sync = fair ? new FairSync() : new NonfairSync();
 }
lock方法的解析

ReentrantLock鎖的使用的入口在lock方法,下面我們針對公平鎖lock方法的實現進行分析一波(能看懂這個相信對非公平鎖的lock的實現的理解也就不會有什麼難度了)。併發

這裏我把全部的方法都放在一塊兒,方便你們閱讀:函數

//這裏在併發狀況下會有競爭
    final void lock() {
            acquire(1);
    }

    //來至於父類AQS中
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    //公平鎖自身提供的實現方法,來保證鎖的獲取是按照FIFO原則.也就是隊列模型,先入先出。
    protected final boolean tryAcquire(int acquires) {
        //獲取當前線程
        final Thread current = Thread.currentThread();
       //拿到鎖標記的狀態值,爲0則表明這把鎖沒人佔用
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                //將幹活的人的身份標記一下
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            //這裏是重入鎖的關鍵代碼,只要是獲取鎖的線程再次去拿這把鎖,則能夠直接獲取成功,
            //並將state的值+1後從新設置,供後面釋放鎖的時候進行屢次釋放使用。
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            //這裏有個優雅的小細節:我們發現設置狀態時並無使用compareAndSetState這種方法,
            //而是直接設置。那是由於在這種條件下不會有競爭,只多是獲取鎖的線程才能去改變這個值。
            setState(nextc);
            return true;
        }
        return false;
    }

    //用來判斷是否在它以前已經有人排在隊列當中了,若是有,則返回true
    public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
        //這裏返回時的判斷條件可能有點難理解。假設當前是A線程。
        //1.第一種狀況發生在有一個B線程進度比A快,已經準備開始排隊了。能夠看下面addWaiter方法
        //的調用,在進行compareAndSetTail交換後,有可能還沒來得及將pred.next指向這個新節點node,
        //這個時候說明已經有人在A線程前面去排隊拿鎖了。
       //2.第二種狀況簡單明瞭。A線程不是排在隊列的第一個的,也證實了有人排在他前面了。
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

    //用來添加新的節點到隊列的尾部。
    private Node addWaiter(Node mode) {
        //根據傳進來的參數mode=Node.EXCLUSIVE,表示將要構造一個獨佔鎖。
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        //tail爲空的狀況下直接調用enq方法去進行head和tail的初始化。
        if (pred != null) {
           //tail不爲空的狀況下,將新構造節點的前驅設置爲原尾部節點。
            node.prev = pred;
           //使用CAS進行交換,若是成功,則將原尾部節點的後繼節點設置爲新節點,作雙向列表關聯;
           //(這裏要注意一點,交換成功的同時有其餘線程讀取該列表,有可能讀取不到新節點。例如A線程
           //執行完下方步驟1後,還未執行步驟2,遍歷的時候將會獲取不到新節點,這也是
           //hasQueuedPredecessors方法中的第一種狀況)
           //若是不成功,則表明有競爭,有其餘線程修改了尾部,則去調用下方enq方法
            if (compareAndSetTail(pred, node)) {   //1
                pred.next = node;   //2
                return node;
            }
        }
        enq(node);
        return node;
    }

     private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                //初始化head和tail,初始化完成後,會繼續執行外面的死循環,進行compareAndSetTail將
               //新節點設置到尾部,和上述執行流程同樣,這裏就不詳述了。
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

    //再進行一次嘗試和進入堵塞
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //獲取當前node的前驅
                final Node p = node.predecessor();
                //若是前驅是head的話就再進行一次嘗試,這種設計會節約不少的資源。
                //這裏嘗試成功後該線程就不會有後續的park和unpark之說了。
                if (p == head && tryAcquire(arg)) {
                     //若是獲取成功就將head設置成當前node,並將存儲的thread和prev都清空
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    //來判斷進行嘗試獲取失敗後是否進行park
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //node的waitStatus初始化都是0
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //第一次進來確定不是-1狀態的,須要compareAndSetWaitStatus方法進行設置後纔會是-1
            return true;
        if (ws > 0) {
           //這裏的做用是用來剔除被cancel後的節點,只要是cancel後的節點waitStatus 都會被標記成1。
            //用該狀態來過濾掉這些節點。
           //因爲節點的喚醒是由它的prev節點來進行喚醒的,咱們必需要保證它的prev是處於活着的狀態
           //因此這裏一直遍歷往上找,總會找到一個正常的prev來幫助其unpark。
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
           //設置prev爲-1狀態,(該狀態下可以喚醒它的下一個去幹活)。
            //這裏結束後會跳到acquireQueued的死循環再次循環一次。
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    
    //要執行這個方法的前提是shouldParkAfterFailedAcquire這個方法必須返回true
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    
    //阻塞線程的方法
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        // 設置Blocker,設置爲當前lock。
        setBlocker(t, blocker);
        // 等待獲取許可,這裏會進行堵塞,直到有人幫忙調用該線程的unpark方法纔會獲取到許可,
        //並繼續走下面的流程。
        UNSAFE.park(false, 0L);
        // 設置Blocker,將該線程的parkBlocker字段設置爲null,這個是在線程被喚醒後執行的。
        setBlocker(t, null);
    }
unlock方法的解析
//調用該方法進行解鎖
    public void unlock() {
        sync.release(1);
    }
    
    //改變state的值並喚醒隊列中的下一個線程來幹活
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            //這裏會判斷頭部是否是null,並看其waitStatus 狀態是否有喚醒它的後繼節點的資格。
            //這裏的頭部其實也就是當前線程所表明的節點。
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }  

    //嘗試着釋放鎖
    protected final boolean tryRelease(int releases) {
            //將鎖標記state的值-1
            int c = getState() - releases;
            //若是幹活的人和本身的身份不一致,則拋異常出去
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //這裏判斷狀態-1後是否是等於0。
            //若是不是,則表明重入了不少次,鎖暫時不釋放。
            //若是是,則將free置爲true,釋放鎖,將身份標記置爲null。
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
    }

    //去喚醒後繼節點中的thread來幹活
    private void unparkSuccessor(Node node) {
    
        int ws = node.waitStatus;
        //若是head中的waitStatus<0,則置爲0
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        //這裏會檢查head的下一個節點是否是null以及是不是cancel狀態
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            //若是next是cancel狀態,則將s置爲空,並重隊列尾部進行往前遍歷,直到找到最後
            //一個waitStatus <=0的node來作爲next節點去喚醒
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            //去喚醒s指向的next節點,調用這裏可讓UNSAFE.park(false, 0L);處的線程獲取到許可。
            //到這裏解鎖的功能就執行完畢了~
            LockSupport.unpark(s.thread);
    }

總結

ReentrantLock.png


擴展

擴展個ReentrantReadWriteLock 讀鎖獲取鎖的流程圖
ReentrantReadWriteLock.pngui


End

相關文章
相關標籤/搜索