圖解AQS原理之ReentrantLock詳解-公平鎖

概述

前面已經講解了關於AQS的非公平鎖模式,關於NonfairSync非公平鎖,內部其實告訴咱們誰先爭搶到鎖誰就先得到資源,下面就來分析一下公平鎖FairSync內部是如何實現公平的?若是沒有看過非公平鎖的先去了解下非公平鎖,由於這篇文章前面不會講太多內部結構,直接會對源碼進行分析java

前文鏈接地址:圖解AQS原理之ReentrantLock詳解-非公平鎖segmentfault

本文分析的JDK版本是1.8

舒適提示:讀本文內容建議結合以前寫的非公平,前篇設計了不少基礎性內容less

源碼分析

在源碼分析以前,咱們先來看一下ReentrantLock如何切換獲取鎖的模式呢?實際上是在構造器中傳遞指定的類型變量來控制使用鎖的方式,以下所示:函數

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

fair參數指定爲true時,表明的是公平鎖,若是指定爲false則使用的非公平,無參的構造函數默認使用的是非公平模式,以下所示:源碼分析

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

接下來咱們以一個例子來進行後面的說明:ui

public class ReentrantLockDemo {

    public static void main(String[] args) throws Exception {
        AddDemo runnalbeDemo = new AddDemo();
        Thread thread = new Thread(runnalbeDemo::add);
        thread.start();
        Thread.sleep(500);
        Thread thread1 = new Thread(runnalbeDemo::add);
        thread1.start();
        System.out.println(runnalbeDemo.getCount());
    }

    private static class AddDemo {
        private final AtomicInteger count = new AtomicInteger();
        private final ReentrantLock reentrantLock = new ReentrantLock(true);
        private final Condition condition = reentrantLock.newCondition();

        private void add() {
            try {
                reentrantLock.lockInterruptibly();
                count.getAndIncrement();
            } catch (Exception ex) {
                System.out.println("線程被中斷了");
            } finally {
//                reentrantLock.unlock();
            }
        }

        int getCount() {
            return count.get();
        }
    }
}

咱們經過源碼能夠看到這裏咱們啓動了兩個線程,兩個線程分別進行同步鎖操做,這裏我並無釋放掉鎖,由於方便分析隊列的狀況,固然你也能夠在內部寫一個死循環,不釋放鎖就能夠了,我這裏簡單的不釋放鎖,使用的是可中斷的獲取鎖操做方法lockInterruptibly,這裏內部的原理咱們上一篇文章中已經講解過了,這裏並不過多的去分析內部原理,這個ReentrantLocklockInterruptibly調用內部類AQSacquireInterruptibly,可是實際上是FairSync內部類繼承了內部類Sync,而內部類Sync有繼承了AbstractQueuedSynchronizer簡稱AQS,acquireInterruptibly源碼信息以下所示:this

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

這裏咱們經過上一篇文章得知tryAcquire是須要子類去實現的方法,咱們在例子中指定了使用的是公平鎖,因此tryAcquire方法的實現是在ReentrentLockFairSync類中,咱們來具體看一下這個方法,重點也在這個方法中其餘的其實都是同樣的,由於用的方法都會同樣的非公平和公平鎖的調用,惟獨不同的就是子類實現的方法是不相同的,接下來咱們就來看一下公平鎖的tryAcquire是如何實現的?線程

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;                                            //state遞增
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);                                                            //設置state狀態
        return true;                                                                    //得到鎖成功
    }
    return false;                                                                            //得到鎖失敗
}

對比非公平鎖的NonfairSync類的tryAcquire方法,其實就是在鎖可用的狀況下增長了一個判斷條件,這個判斷方法就是hasQueuedPredecessors,從方法的名稱來看說的是有等待的線程隊列,換句話說已經有人在排隊了,新來的線程你就不能加塞,而非公平模式的誰先爭搶到鎖就是誰的,管你先來不先來,接下來咱們具體看一下這個設計

hasQueuedPredecessors方法源碼:code

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; // 得到尾節點
    Node h = head; // 得到頭節點
    Node s;
    return h != t &&    //頭節點和尾節點相同表明隊列爲空        
        ((s = h.next) == null || s.thread != Thread.currentThread());    //頭節點的next節點爲空表明頭節點,以及s.thread不是當前線程不是本身的話表明隊列中存在元素
}

經過上面的源碼信息,能夠得出其實內部主要就是判斷有沒有排隊等待的節點,隊列是否爲空,若是爲空的話則能夠爭搶鎖,若是隊列不爲空,夥計你必須老老實實給我排隊去,除非佔有鎖的線程和請求鎖的線程是同樣的,不然仍是老老實實排隊去,這就是公平模式的鎖操做,還有一個lock方法,公平模式的lock方法,沒有直接上來先獲取鎖,而是先嚐試得到鎖直接調用AQSaquire方法進行嘗試獲取鎖,下面是FairSync源碼:

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

    final void lock() {
        acquire(1);                                    //這裏直接調用了aquire並無嘗試修改state狀態
    }

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

結束語

本內容主要是結合上篇內容的一個續篇,能夠結合上篇而後再看下篇會比較清晰些。

相關文章
相關標籤/搜索