ReentrantLock與公平鎖、非公平鎖實現

前言
最近開始讀JDK源碼,所有心得準備總結成一個專欄,JDK Analysis系列的第一篇,就從萬衆矚目的ReentrantLock開始吧,而談到ReentrantLock,就不得不說AQS,它是AbstractQueuedSynchronizer類的簡稱,Doug Lea上神在JDK1.5將其引入,這纔有了現在的併發包java.util.concurrent,所以要理解ReentrantLock的原理,AQS也是必須要搞懂的。這篇就先闡述ReentrantLock最基本的公平鎖和非公平鎖的實現,以及部分涉及的AQS原理,AQS源碼解讀將在後續跟進。整個系列基於JDK1.8.0_92。

公平鎖與非公平鎖
大家都知道,在JDK1.5之前,我們在多線程的環境下要想保證線程安全,就必須要使用synchronized關鍵字來實現對象鎖或者類鎖,以此滿足這樣的需求,JDK1.5之後則使用Lock來實現更加細粒度的鎖。在剛接觸Java的時候,學到這兩種方式的時候,粗略地知道後者更加貼近面向對象的思想,但是在工作中遇到一些奇奇怪怪的需求的時候,只是知道這個是遠遠不夠的。所謂公平鎖,就是線程按照執行順序排成一排,依次獲取鎖,但是這種方式在高併發的場景下極其損耗性能;這時候,非公平鎖應運而生了,所謂非公平鎖,就是不管執行順序,每個線程獲取鎖的機率都是相同的,獲取失敗了,纔會採用像公平鎖那樣的方式。這樣做的好處是,JVM可以花比較少的時間在線程調度上,更多的時間則是用在執行邏輯代碼裏面。

公平鎖、非公平鎖的創建方式:

//創建一個非公平鎖,默認是非公平鎖
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);

//創建一個公平鎖,構造傳參true
Lock lock = new ReentrantLock(true);

相關源碼:

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

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

這部分源碼比較簡單,這裏對於源碼就不贅述。

NonfairSync 非公平鎖
在談NonfairSync之前,首先要談談ReentrantLock類裏面定義的一個類屬性Sync,它纔是ReentrantLock實現的精髓。它首先在屬性裏聲明,然後以抽象靜態內部類的形式實現了AQS,源碼如下:

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        //聲明的lock()方法,供子類實現
        abstract void lock();

        //非公平鎖的獲取方式,相較於公平鎖的tryAcquire()
        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;
        }

        //釋放鎖
        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;
        }

        //判斷當前線程是否是鎖的持有者
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        //獲取當前鎖持有線程,如果在隊列中等待獲取鎖,則返回null
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        //返回當前線程status的狀態,如果持有鎖就讀取status,沒有就0
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        //是否上鎖
        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

此後,NonfairSync繼承它來實現非公平鎖,FairSync繼承它來實現公平鎖,AQS提供一個tryAcquire()的模板方法來使得公平鎖和非公平鎖的實現方式顯得靈活。我們來看看NonfairSync的源碼:

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

如代碼所示,在lock的時候,先是嘗試將AQS的status從0設爲1,成功的話就把當前線程設置爲鎖的持有者,如果嘗試失敗了,基於模板方法,實際調用的是Sync的nonfairTryAcquire(int acquires)方法,該方法源碼如下:

       final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //ReentrantLock是可重入鎖是這裏實現的
            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;
        }

首先獲取當前線程,和當前AQS維護的鎖的狀態,如果狀態爲0,則嘗試將AQS的status從0設爲acquires(實際是1),如果設置成功,則獲取鎖成功,把當前鎖設置爲鎖的持有者,返回true;如果當前線程已經是鎖的持有者,則把status+acquires,如果結果越界,拋出異常,如果成功,返回true。細心的同學可以發現,一共有兩次原子設status從0到1,爲什麼呢?因爲這樣可以提高獲取鎖的概率,因爲是非公平的,所以有必要進行這樣的操作,而且這樣的操作與鎖相對來講損耗微乎其微。

FairSync 公平鎖
公平鎖就是每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果爲空,或者當前線程線程是等待隊列的第一個,就佔有鎖,否則就會加入到等待隊列中,以後會按照FIFO的規則從隊列中獲取,下面是FairSync 的源碼:

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

        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;
        }
    }

來看子類的 tryAcquire方法,與非公平鎖比較,獲取鎖的操作只有一點不同,就是加入了hasQueuedPredecessors() 方法,該方法又大有來頭,ctrl進去是這的:

    public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
        //head沒有next ----> false
        //head有nextnext持有的線程不是當前線程 ----> true
        //head有nextnext持有的線程是當前線程 ----> false
        return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
    }

該方法的簽名是:查詢是否有其他線程比當前線程等待獲取鎖花費了更多的時間。在AQS中對線程是做了一個FIFO隊列,這裏的tail是尾,head是頭,具體的實現會在後續跟進,這裏就不多做贅述,有意思的是return那一行,其中的意思在上面做了解答,查詢是否有其他線程比當前線程等待獲取鎖花費了更多的時間,有就返回true,沒有就返回false,也就是說該方法返回false,才進行addWaiter狀態的更改嘗試,其餘和部分和非公平鎖的部分一樣。

ctrl點進acquire(1)是這樣的:

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

首先通過tryAcquire方法嘗試獲取鎖,如果成功直接返回,否則通過acquireQueued()再次嘗試獲取。在acquireQueued()中會先通過addWaiter將當前線程加入到CLH隊列的隊尾,在CLH隊列中等待。在等待過程中線程處於休眠狀態,直到成功獲取鎖纔會返回。

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