1. 程式人生 > >ReentrantLock原始碼之公平鎖與非公平鎖

ReentrantLock原始碼之公平鎖與非公平鎖

ReentrantLock類與Synchronized關鍵字的主要區別之一就是可以實現公平鎖和非公平鎖。我們看下ReentrantLock類是如果實現公平鎖與非公平鎖的。

1、非公平鎖的實現

ReentrantLock預設建構函式為非公平鎖(為什麼?因為非公平鎖的實現可以減少執行緒的切換,提高執行效率。)

public ReentrantLock() {
        sync = new NonfairSync();
    }

ReentrantLock中含有一個內部類NonfairSync,如下

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        /**
         * 非公平lock方法。首先,直接呼叫AQS的compareAndSetState方法,原子設定同步狀態的值。
         *      True,表明獲取鎖成功,設定AQS的成員變數獨佔鎖的擁有者exclusiveOwnerThread為當前        
         *         執行緒
         *      False,獲取鎖失敗。呼叫AQS的acquire方法。
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

         //實現父類的tryAcquire方法。嘗試以獨佔模式獲取同步狀態。
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

ReentrantLock在呼叫lock方法時,其實呼叫的是NonfairSync 類的lock方法,此方法先呼叫AQS的模板方法compareAndSetState來原子的設定同步狀態的值,如果成功,則表明獲取鎖成功,失敗,則呼叫AQS的模板方法acquire()。呼叫AQS的模板方法acquire()如下

//該方法呼叫NonfairSync類中重寫的tryAcquire方法
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();// 如果沒有獲取到同步狀態,並且最終執行緒的中斷狀態為true,則會呼叫selfInterrupt方法進行執行緒中斷。
    }

acquire方法是AQS實現的模板方法。tryAcquire方法需要具體的子類進行實現,這個方法一會再看。先看兩個方法addWaiter()和acquireQueued() 此兩個方法均為AQS的模板方法,分別如下

//addWaiter方法是AQS的模板方法,實現建立節點並進入同步佇列。
private Node addWaiter(Node mode) {
        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;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
//acquireQueued是AQS的模板方法,當前執行緒會根據公平原則來進行阻塞等待,直到獲取到同步狀態位置。並且會返回在阻塞過程中執行緒是否被中斷過。
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)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

現在來看AQS的子孫類NonfairSync實現的tryAcquire方法

 //實現父類的tryAcquire方法。嘗試以獨佔模式獲取同步狀態。
 protected final boolean tryAcquire(int acquires) {
     return nonfairTryAcquire(acquires);
 }

nonfairTryAcquire()方法在AQS的子類Sync類中已實現,如下。(為什麼放到Sync類中?

//Sync實現的nonfairTryAcquire方法。
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {//同步狀態為0,則再次設定同步狀態,成功,則返回True
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//同步狀態不為0,當佔用鎖的執行緒為當前執行緒時,重入,返回成功。
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

2、公平鎖的實現

公平鎖呼叫Lock方法時,並沒有上來呼叫compareAndSetState方法(因為不知道同步佇列中是否有執行緒等待)。首先呼叫AQS的模板方法acquire方法,addWaiter方法與acquireQueued呼叫的邏輯相同,主要看公平鎖版本tryAcquire方法的實現。

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        final void lock() {
//注意這裡與公平鎖實現的不同,公平鎖不能直接呼叫CAS方法,因為公平鎖需要確認同步佇列中是否有等待獲取同步狀態的執行緒
            acquire(1);
        }
        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        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;
        }
    }

我們發現與非公平鎖相比,主要多一句!hasQueuedPredecessors,這是實現公平鎖和非公平鎖的關鍵。hasQueuedPredecessors方法是查詢同步佇列中是否有等待獲取鎖的執行緒,只有返回false時,才會設定同步狀態的值。否則,加入同步佇列阻塞等待獲取鎖。提現公平鎖的特點。

參考文章:

(1)JUC--AQS原始碼分析(二)同步狀態的獲取與釋放

(2)《Java併發程式設計的藝術》第五章Java中的鎖。