ReentrantLock公平鎖的獲取和釋放源碼學習

ReentrantLock公平鎖的測試代碼如下:

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock(true);//創建一個公平鎖
        lock.lock();
        try{
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception e){

        }finally{
            lock.unlock();
        }
    }

ReentrantLock的構造函數:

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

當參數fair爲true時則創建FairSync類,當fair爲false時創建NonfairSync類

接下來進入lock.lock()方法查看具體獲取鎖的邏輯:

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

查看sync的lock()方法可以發現,sync類中的lock()方法爲一個鉤子方法,因爲上面創建的是公平鎖,所以此時具體的獲取鎖資源的實現是在FairSync類中。

在這裏插入圖片描述

FairSync類的lock()方法

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

AQS的acquire(int arg)方法是模板方法,公平模式和非公平模式都是用同樣的流程

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

嘗試獲取鎖資源該方法的邏輯大致如下:

  1. 判斷當前鎖資源是否被某個線程佔用
  2. 如果鎖資源已經被佔用,則判斷佔用鎖資源的線程是否是當前線程,如果是當前線程,則直接對AQS的狀態變量加acquires,表示重入
  3. 如果當前鎖資源沒有被佔用,判斷AQS隊列中優先級最高的節點節點所對應的線程是否是當前線程
  4. 如果是當前線程,則獲取鎖成功
  5. 如果不是當前線程,意味着AQS隊列中有更高優先級的線程,於是將當前線程封裝成一個Node節點,並將這個節點添加到AQS同步隊列的隊尾
  6. 阻塞當前線程,等待後續被喚醒
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//獲取當前線程
            int c = getState();//獲取AQS中狀態變量的值
            if (c == 0) {//c == 0表示當前沒有以其他線程佔用鎖資源
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//如果佔用鎖資源的線程是當前線程
                int nextc = c + acquires;//AQS狀態變量值+1
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

	//判斷當前線程節點在AQS同步隊列中是否還有前驅節點    
	public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
  1. h != t 表示AQS的同步隊列中至少有兩個節點

  2. (s = h.next) == null 處理的是一種中間狀態

在這裏插入圖片描述

線程節點入隊的方法如上圖所示:

​ 1、先將尾節點設置爲當前節點的prev

​ 2、cas設置隊列的尾節點

​ 3、將舊爲節點的next指向當前節點

(s = h.next) == null ,就是用來處理當前有一個其他線程剛好執行到上面的步驟2,但還沒來得及執行步驟3的情況,此時將會返回true,因爲當前線程不是隊列中優先級最高的線程。

  1. s.thread != Thread.currentThread() 判斷隊列中第二個節點所維護的線程是否是當前線程

其餘封裝線程爲一個節點並添加到AQS同步隊列的邏輯通非公平模式相同

//將當前線程封裝成一個Node節點,並添加到AQS的同步隊列中    
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();//獲取當前節點的前驅節點
                if (p == head && tryAcquire(arg)) {//如果前驅節點是head節點,則嘗試獲取鎖資源
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章