ReentrantLock和AQS解析

本文參考了https://blog.csdn.net/javazejian/article/details/75043422
ReentrantLock是在多線程問題處理中常用的鎖結構,作爲一種排它鎖 他比 synchronized要靈活,使用上卻要比 synchronized複雜一些。
要了解ReentrantLock的原理就不得不提到AQS,AQS 即 AbstractQueuedSynchronizer 的縮寫 翻譯爲抽象的同步隊列。提供了以Node爲基礎構建的雙向鏈表存儲請求鎖的線程。
我們來看以下ReentrantLock的類結構
在這裏插入圖片描述
我們這裏關注他的三個內部類,公平鎖 ,非公平鎖 和 Sync
在這裏插入圖片描述
可以看到Sync正是繼承自 AbstractQueuedSynchronizer,我們再來看一下AbstractQueuedSynchronizer
在這裏插入圖片描述

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {

    /** Use serial ID even though all fields transient. */
    private static final long serialVersionUID = 3737899427754241961L;

    /**
     * Empty constructor for use by subclasses.
     */
    protected AbstractOwnableSynchronizer() { }

    /**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * Sets the thread that currently owns exclusive access.
     * A {@code null} argument indicates that no thread owns access.
     * This method does not otherwise impose any synchronization or
     * {@code volatile} field accesses.
     * @param thread the owner thread
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    /**
     * Returns the thread last set by {@code setExclusiveOwnerThread},
     * or {@code null} if never set. This method does not otherwise
     * impose any synchronization or {@code volatile} field accesses.
     * @return the owner thread
     */
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

這個類就很簡潔了,他定義了當前持有鎖的線程。我們再回頭來看AbstractQueuedSynchronizer。
在這裏插入圖片描述
我們來看一下Node的定義

static final class Node {
        /** 標記爲共享鎖*/
        static final Node SHARED = new Node();
        /** 標記爲排他鎖*/
        static final Node EXCLUSIVE = null;
        //以下4個狀態都是 waitStatus 所支持的狀態 標識當前Node 中線程的狀態 
        /** 當前線程可以取消 */
        static final int CANCELLED = 1;
        /** 當前線程可以被喚醒 */
        static final int SIGNAL = -1;
        /** 當前線程等待condition 條件 */
        static final int CONDITION = -2;
        /**
         * 在共享模式中使用表示獲得的同步狀態會被傳播
         */
        static final int PROPAGATE = -3;

        /** 等待狀態 */
        volatile int waitStatus;

        /** 上一個節點*/
        volatile Node prev;

        /** 下一個節點*/
        volatile Node next;

        /** 當前節點持有的線程*/
        volatile Thread thread;

        /**
         * 等待隊列中的後繼結點,這個與Condition有關
         */
        Node nextWaiter;

        /**
         * 是否共享鎖
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * 獲取上一個節點
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() { // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) { // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

看完了Node 我們來看一下AbstractQueuedSynchronizer 自身的屬性

/**
     * 隊首
     */
    private transient volatile Node head;

    /**
     * 隊尾
     */
    private transient volatile Node tail;

    /**
     *  鎖狀態  爲 0 時 沒有線程持有鎖
     */
    private volatile int state;

在AbstractQueuedSynchronizer 中提供了很多方法維護Node的數據結構,以及定義了獲取鎖和釋放鎖的基礎方法。瞭解了他們的內部結構,我們來看這張圖
在這裏插入圖片描述
這張圖描述了AQS的相關類的關係。
AbstractOwnableSynchronizer:抽象類,定義了存儲獨佔當前鎖的線程和獲取的方法

AbstractQueuedSynchronizer:抽象類,AQS框架核心類,其內部以虛擬隊列的方式管理線程的鎖獲取與鎖釋放,其中獲取鎖(tryAcquire方法)和釋放鎖(tryRelease方法)並沒有提供默認實現,需要子類重寫這兩個方法實現具體邏輯,目的是使開發人員可以自由定義獲取鎖以及釋放鎖的方式。

Node:AbstractQueuedSynchronizer 的內部類,用於構建虛擬隊列(鏈表雙向鏈表),管理需要獲取鎖的線程。

Sync:抽象類,是ReentrantLock的內部類,繼承自AbstractQueuedSynchronizer,實現了釋放鎖的操作(tryRelease()方法),並提供了lock抽象方法,由其子類實現。

NonfairSync:是ReentrantLock的內部類,繼承自Sync,非公平鎖的實現類。

FairSync:是ReentrantLock的內部類,繼承自Sync,公平鎖的實現類。

ReentrantLock:實現了Lock接口的,其內部類有Sync、NonfairSync、FairSync,在創建時可以根據fair參數決定創建NonfairSync(默認非公平鎖)還是FairSync。
接下來我們從一個實例出發,從源碼上看一個線程從獲取鎖到釋放鎖的過程。

/**
 * @Description
 * @Author changyandong
 * @Emoji (゜ - ゜)つ乾杯
 * @Created Date: 2020/4/1 9:12
 * @ClassName TestThread
 * @Version: 1.0
 */
public class TestThread implements Runnable {
    static ReentrantLock reentrantLock = new ReentrantLock();
    public static int i = 0;
    @Override
    public void run() {

        reentrantLock.lock();
        try {
            IntStream.range(0,10000).forEach(k->i++);
        }finally {
            reentrantLock.unlock();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        Thread thread1 = new Thread(testThread);
        Thread thread2 = new Thread(testThread);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(TestThread.i);
    }
}

我啓動兩個線程操作共享資源i 我們先來分析lock()

 public void lock() {
        sync.lock();
    }
 // 默認情況下我們使用的是非公平鎖實現  NonfairSync
    final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

可以看到,他嘗試使用cas將state從0設置爲1,如果設置成功,當前線程就是持有鎖的線程否則acquire(1);
acquire是AbstractQueuedSynchronizer中的方法定義如何獲取鎖和進入等待隊列

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

我們這裏先看看tryAcquire

// 非公平鎖的嘗試獲取
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
              // cas 如果當前沒有人持有鎖 設置state 爲1 
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果是重入 將state + 1
            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;
        }

如果這裏返回false表示嘗試獲取鎖失敗,那麼就應該將當前線程置入隊列中也就是acquireQueued的邏輯,但是在他之前我們看一下addWaiter()
addWaiter() 也是 AbstractQueuedSynchronizer定義的創建node並加入隊尾的方法

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的上一個節點爲  之前的隊尾
            node.prev = pred;
            // cas 嘗試將 tail設置爲node
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // cas設置失敗 
        enq(node);
        return node;
    }

private Node enq(final Node node) {
       // 無限循環的for
        for (;;) {    
            // 獲取隊尾
            Node t = tail;
            // 當前隊列還沒初始化
            if (t == null) { // Must initialize
            // 設置隊首,並將隊首設爲隊尾
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            // 否則一直嘗試cas設置 隊尾 直到 成功
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

接着來看 添加隊列方法acquireQueued

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
           // 無限循環
            for (;;) {
            // 獲取 node 的前置節點 
                final Node p = node.predecessor();
            // 如果前置爲 head 那麼就嘗試獲取鎖
                if (p == head && tryAcquire(arg)) {
                // 獲取鎖成功,當前node就變成了 head 並釋放原head 
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }   
               //如果p不是head 或者獲取鎖失敗,那麼判斷是否需要Park線程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
        // 如果出了問題 比如 shouldParkAfterFailedAcquire的空指針  那麼放棄 node
            if (failed)
                cancelAcquire(node);
        }
    }

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // 獲取上一個節點的等待狀態
        int ws = pred.waitStatus;
        // 如果是喚醒狀態 返回true
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        // 如果狀態大於 0 說明線程應該被結束 釋放節點
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        // ws = 0 或者 -3  將它cas爲 -1
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE. Indicate that we
             * need a signal, but don't park yet. Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
     // 鎖住當前線程
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

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

        node.thread = null;

        // Skip cancelled predecessors
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
        Node predNext = pred.next;

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // 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
        }
    }

到這裏 lock方法的流程就結束了。接下來看unLock() 這個就相對簡單了

public void unlock() {
        sync.release(1);
    }

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            // 如果鎖已經沒有線程持有了 就喚醒一個隊列裏的Node
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
//tryRelease 很簡單 就是改state  如果 state爲0 將持有線程設置爲0
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;
        }

 private void unparkSuccessor(Node node) {
        /*
         * 設置waitStatus爲 0
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
          * 判斷下一個節點是否應該被喚醒,如果不是 倒序找到一個需要被喚醒的節點 喚醒 他
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            // 這裏循環沒有退出  找到的是node節點的後續節點中最早進入隊列的一個  所有unpark後 他的prev 就是head 
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

在這裏我再放上一張流程圖
在這裏插入圖片描述
unLock 過程
在這裏插入圖片描述
接下來我們來看公平鎖,公平鎖保證嚴格的FIFO,不會再acquire之前先嚐試設置state,公平鎖與非公平鎖的區別就在tryAcquire()方法的實現略有區別。

  protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 如果c == 0 hasQueuedPredecessors 判斷的是 隊列是否爲 空或者 當前線程是重入才執行cas操作
                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;
        }
    }

  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.
        //  aqs 會維護head 賦值一定在tail 之前,這樣我們獲取時就要先獲取tail 保證線程安全
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        // h== t 隊列爲空 h.next == null  隊列爲空  s.thread == Thread.currentThread() 重入 因爲下一個獲取鎖的線程爲本線程所以符合公平鎖定義
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

下面放上我自己畫的 lock流程
在這裏插入圖片描述
關於Condition 之後再補充

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