難以理解的鎖(一) ReentrantLock

一、AbstractQueuedSynchronizer

屬性

屬性 類型 作用
state int 表示是否被線程持有,0表示沒有,n表示線程重入次數
head Node 線程阻塞隊列的頭節點,head表示正在持有鎖的線程
tail Node 線程阻塞隊列的尾節點,未競爭到鎖的Node將添加至隊列尾部
unsafe Unsafe 含有CAS原子操作的類,調用本地方法實現
exclusiveOwnerThread Thread 繼承AbstractOwnableSynchronizer類的字段,表示當前持有鎖的線程

Node 屬性

屬性 類型 作用
thread Thread 線程對象
prev Node 指向隊列中前一個節點
next Node 指向隊列中後一個節點
waitStatus int 等待狀態,1表示線程取消等待,-1表示後一個節點該被喚醒,-2,-3暫時不用

鎖實現的思路:
1 CAS操作更新state,比如將0更新爲1,成功表示競爭到了鎖,操作失敗則競爭失敗,需要加入阻塞隊列
2 將線程對象封裝爲Node,加入隊列尾部,可能同時有多個線程操作,所以這一步也是CAS操作
3 解鎖操作將state更新爲0,喚醒隊列中下一個線程Node,解鎖的線程Node從隊列中移除

二、ReentrantLock

ReentrantLock 中有個 Sync 屬性,Sync 抽象類繼承自 AQS,在 ReentrantLock 內部有非公平(NonfairSync)和公平(FairSync)兩種實現,先學習 FairSync。

結合場景理解代碼:

線程 t1 首次申請

假設只有線程t1獲取鎖,執行lock方法:
FairSync#lock

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

AbstractQueuedSynchronizer#acquire

    public final void acquire(int arg) {
    	// 嘗試獲取鎖
    	// 獲取失敗表示競爭且失敗,加入隊列中(具體邏輯後面分析)
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

FairSync#tryAcquire

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // state=0表示可競爭狀態
            if (c == 0) {
            	// 如果隊列中沒有等待線程(公平鎖,如果隊列有等待的線程,當前線程就沒有資格獲取鎖了)
            	// 再CAS操作更新state,失敗說明同一時間其他線程獲取到了鎖
            	// 成功設置獨佔標記爲當前線程
                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;
            }
            // 返回fasle表示鎖已經被持有或者競爭失敗
            return false;
        }

如果只有一個線程t1獲取鎖,則在tryAcquire方法CAS這一步成功獲取到鎖。

線程 t2 獲取已被佔有的鎖

如果在 t1持有鎖 的狀態下,t2線程獲取鎖,執行tryAcquire一定是失敗的,這時會執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)這一句。
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;
        // 如果隊尾不爲空,CAS更新tail,成功則將調整隊列指向至正確,返回當前node
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 如果tail指向是空或者CAS更新失敗執行enq
        enq(node);
        return node;
    }

AbstractQueuedSynchronizer#enq

    private Node enq(final Node node) {
    	// 循環
        for (;;) {
            Node t = tail;
            // 如果tail爲null說明隊列爲空,初始化隊列,這裏也會存在競爭所以依然CAS操作
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	// CAS更新隊尾Node直到成功,返回隊尾當前線程node
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

這時隊列爲空,需要初始化隊列,然後t2線程node加入隊列:
在這裏插入圖片描述

AbstractQueuedSynchronizer#acquireQueued

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	// 獲取前一個node
                final Node p = node.predecessor();
                // 前一個node是頭,嘗試獲取鎖,如果成功當前node成爲頭,解放原head
                // 嘗試獲取失敗說明還有線程在持有
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 不是head或者獲取鎖失敗,設置前個node中waitState標記爲-1即等待狀態
                if (shouldParkAfterFailedAcquire(p, node) &&
                	// 已經是-1時,停止當前線程
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
            	// 執行到這裏說明出現異常,取消獲取,後面再分析
                cancelAcquire(node);
        }
    }

AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // 如果-1表示下個節點需要喚醒
        if (ws == Node.SIGNAL)
            return true;
        // 如果1表示線程取消鎖競爭,將其這些移除,直到找到一個不是取消狀態的指向自己
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
		// 0 -2 -3 則將設置爲-1
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

AbstractQueuedSynchronizer#parkAndCheckInterrupt

    private final boolean parkAndCheckInterrupt() {
    	// 停止當前線程,底層用了unsafe.park
        LockSupport.park(this);
        // 返回線程中斷狀態,先不管
        return Thread.interrupted();
    }

在 線程t1 還在持有鎖的情況下,上面代碼的目的是將當前線程node的前驅node狀態(waitState)設置爲-1,表示下一個node的線程需要被喚醒。同時將當前線程停止:
在這裏插入圖片描述
假設在 線程t1 在持有鎖狀態下,又來一個線程:
在這裏插入圖片描述

線程 t1 解鎖

解鎖調用unlock方法,同一時間只會有一個線程解鎖
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;
    }

Sync#tryRelease

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            // 如果解鎖的線程不是佔有鎖的線程,拋出非法異常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // state爲0表示沒有線程佔有鎖,設置exclusiveOwnerThread爲null表示沒有線程佔有
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            // 更新state
            setState(c);
            return free;
        }

AbstractQueuedSynchronizer#unparkSuccessor

    private void unparkSuccessor(Node node) {
		// 這裏node是head,如果waitState<0,更新狀態爲0
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

		// 可能隊列中有取消的節點,從後往前找到可以需要喚醒的node,忽略已取消的node,爲什麼從後向前?
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        // 如果head的後一個節點不是null,說明隊列有等待的線程,將其喚醒
        if (s != null)
            LockSupport.unpark(s.thread);
    }

隊列中的線程在 AbstractQueuedSynchronizer#acquireQueued 方法的 parkAndCheckInterrupt 方法中阻塞,就是那個for循環,被喚醒後會繼續循環。被喚醒的線程獲取到鎖併成爲head。

            for (;;) {
            	// 前驅node爲head並且獲取到鎖,當前線程競升爲head
                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;
            }

AbstractQueuedSynchronizer#setHead

    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

線程t1 解鎖,喚醒 線程t2 後:
在這裏插入圖片描述
爲什麼從後向前遍歷找需要喚醒的線程?
1 首先滿足 (s == null || s.waitStatus > 0) 纔會進入遍歷,如果head的next節點不是null且waitState<=0就會直接喚醒next的線程。
2 head.next(即s)什麼時候會是null?

                node.prev = t;
                if (compareAndSetTail(t, node)) {	// 1
                    t.next = node;					// 2
                    return t;
                }

在enq方法和addWaiter方法中都有上面代碼片段,t表示tail的node。先將當前node指向tail,CAS更新隊尾node,成功後將tail的next指向當前node。整個操作不是原子的,有可能出現下面兩種情況:
在這裏插入圖片描述
如果類似圖中第二種情況,那麼head.next爲null,但是其實隊列中有等待線程,所以需要從隊尾在遍歷到當前node看看有沒有需要喚醒的node。

如何公平

在 FairSync#tryAcquire 方法中嘗試加鎖時,需要先判斷隊列中是否有線程排隊,如果沒有才進行CAS更新state。先到隊列的線程線程先獲得鎖。

			if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }

AbstractQueuedSynchronizer#hasQueuedPredecessors

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

head == tail 情況有以下兩種情況,一是隊列沒有初始化過都是null,二是隊列中保留一個thread爲null,waitState爲0的node,這兩種情況都表示隊列中已經沒有排隊線程。
在這裏插入圖片描述
還有一種情況就是head.next的線程和當前線程是同一個,返回也是false,因爲滿足可重入的條件。

取消獲取鎖

在acquireQueued方法try-finally的finally代碼塊中有取消獲取鎖的操作,正常情況不會執行,當有異常時執行。**可能發生的異常就是超過了最大鎖計數 和 隊列中當前線程的前驅爲null。**個人認爲兩種都不可能出現(可能太笨了看不懂)。那就看看怎麼取消的吧。
AbstractQueuedSynchronizer#cancelAcquire

    private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;

        node.thread = null;
        
        // 找到前面不是取消狀態的node
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        Node predNext = pred.next;
		// 設置當前node的狀態爲1
        node.waitStatus = Node.CANCELLED;

        // 如果當前node是tail則移除,同時移除了其他取消狀態的node
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
        	// 這段有點複雜,沒太看懂,我理解的是從隊列中移除,如果前驅是head則喚醒後續node的線程
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            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
        }
    }

如何非公平

ReentrantLock默認是非公平實現的,Sync的實現類爲NonfairSync,相較與FairSync其實也只有lock時一點不同:
1 lock方法首先CAS操作嘗試獲取鎖,如果失敗在調用acquire方法
2 tryAcquire中調用nonfairTryAcquire方法,獲取鎖時不考慮隊列中是否有等待線程,直接參與競爭
NonfairSync#lock

        final void lock() {
        	// 直接嘗試獲取鎖
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

Sync#nonfairTryAcquire

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            	// 不需要考慮隊列中是否有等待的線程
                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;
        }

如果tryAcquire失敗也需要進入隊列,與公平鎖一樣。可能出現隊列中線程一直獲取不到鎖,造成鎖飢餓現象。

(20190905)

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