Java Lock接口分析之ReentantReadWriteLock

ReentantReadWriteLock讀寫鎖,在讀線程多餘寫線程的併發環境中能體現出優異的性能,相比於synchronized與ReentrantLock這種獨佔式鎖的模型,ReentantReadWriteLock採用獨佔式寫鎖與共享式讀鎖的方式,大大提升了針對於讀多寫少的多線程環境的系統性能。html

在分析ReentantReadWriteLock源碼前,咱們須要先熟悉下獨佔鎖與共享鎖的基本模型。java

獨佔鎖與共享鎖的基本模型

共享式取與獨佔式取最主要的區在於同一刻可否有多個程同時獲取到同步狀。以文件的例,若是一個程序在文件操做,那麼文件的寫操做均被阻塞,而操做能時進行。寫操做要求對資源的獨佔式訪問,而操做能夠是共享式訪問,兩種不一樣的訪問模式在同一文件或源的訪問狀況node

中,左半部分,共享式訪問資,其餘共享式的訪問均被允,而獨佔式訪問被阻塞,右半部分是獨佔式訪問資,同一刻其餘訪問均被阻塞。緩存

ReentrantReadWriteLock的結構

下面咱們來消息分析下ReentantReadWriteLock的實現過程:多線程

ReentrantReadWriteLock並無繼承ReentrantLock,也並無實現Lock接口,而是實現了ReadWriteLock接口,該接口提供readLock()方法獲取讀鎖,writeLock()獲取寫鎖。併發

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {

    private final ReentrantReadWriteLock.ReadLock readerLock;
    private final ReentrantReadWriteLock.WriteLock writerLock;

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }
}

public interface ReadWriteLock {
    
    Lock readLock();

    Lock writeLock();
}

默認構造方法爲非公平模式 ,開發者也能夠經過指定fair爲true設置爲 公平模式。app

public ReentrantReadWriteLock() {
        this(false);
    }

    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    public static class ReadLock implements Lock, java.io.Serializable {}
    public static class WriteLock implements Lock, java.io.Serializable {}

而公平模式和非公平模式分別由內部類FairSync和NonfairSync實現,這兩個類繼承自另外一個內部類Sync,該Sync繼承自AbstractQueuedSynchronizer(之後簡稱 AQS ),這裏基本同ReentrantLock的內部實現一致。性能

abstract static class Sync extends AbstractQueuedSynchronizer {
}

static final class FairSync extends Sync {
}

static final class NonfairSync extends Sync {
}

而ReentrantReadWriteLock針對FairSync於NonfairSync也有着本身的內部類實現,與ReentrantLock同樣。優化

ReentrantReadWriteLock的鎖標記實現state

在ReentrantLock的分析中得知,其獨佔性和重入性都是經過CAS操做維護AQS內部的state變量實現的。而對於ReentantReadWriteLock由於要維護兩個鎖(讀/寫),可是同步狀態state只有一個,因此ReentantReadWriteLock採用「按位切割」的方式,所謂「按位切割」就是將這個32位的int型state變量分爲高16位和低16位來使用,高16位表明讀狀態,低16位表明寫狀態。ui

高16位 低16位
讀狀態 寫狀態
0000 0000 0000 0011 0000 0000 0000 0000

上面的一個32位的int表示有3個線程獲取了讀鎖,0個線程獲取了寫鎖

讀/寫鎖如何肯定和改變狀態的呢?答案是位運算 !
假設當前同步狀態爲state

//讀狀態:無符號右移16位
state >>> 16
//寫狀態:高16位都和0按位與運算,抹去高16位
state & Ox0000FFFF
//讀狀態加1
state + (1 << 16)
//寫狀態加1
state + 1
//判斷寫狀態大於0,也就是寫鎖是已經獲取
state & Ox0000FFFF > 0
//判斷讀狀態大於0,也就是讀鎖是已經獲取
state != 0 && (state & Ox0000FFFF == 0)

在ReentrantReadWriteLock的源碼中咱們能看到關於鎖標記位state的變量

//定義一個偏移量
static final int SHARED_SHIFT   = 16;
//1個讀鎖讀鎖單位
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);  //Ox00010000
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; //Ox0000FFFF讀鎖上限
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //Ox0000FFFF用於抹去高16位

//返回讀狀態
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
//返回寫狀態
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

首先,咱們先來分析寫鎖的獲取與釋放

寫鎖的獲取

首先,寫鎖WriteLock的lock方法以下

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

 acquire方法實現以下,經過遠嗎咱們能夠看出,acquire方法首先會調用tryAcquire方法嘗試獲取寫鎖,若是獲取成功則返回,失敗則會調用addWaiter方法將線程添加到CLH隊列末尾,並調用acquireQueued方法阻塞當前線程,咱們能夠看到addWaiter依舊是一獨佔的模式添加的節點,此處與ReentrantLock實現同樣具體addWaiter方法與acquireQueued方法獨佔式的實現可詳見個人一篇博客Synchronized與Lock的底層實現解析

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

這裏主要講解tryAcquire方法,代碼以下:

protected final boolean tryAcquire(int acquires) {
    //獲取當前線程
    Thread current = Thread.currentThread();
    //獲取當前同步狀態
    int c = getState();
    //獲取寫鎖狀態(w>0表示已經有線程獲取寫鎖)
    int w = exclusiveCount(c);
    //若是同步狀態不爲0,說明有線程已經獲取到了同步狀態,多是讀鎖,多是寫鎖
    if (c != 0) {
        //寫鎖狀態0(表示有線程已經獲取讀鎖(共享鎖獲取時阻塞獨佔鎖))或者當前線程不是已經獲取寫鎖的線程(獨佔鎖只容許本身持有鎖)
        //返回false
	    //此處的處理邏輯也間接驗證了獲取了讀鎖的線程不能同時獲取寫鎖
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        //大於最大線程數則拋出錯誤
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //若是寫鎖狀態>0而且當前線程爲尺有所線程,則表示寫鎖重入,以重入鎖的方式設置同步狀態(寫狀態直接加)
        //返回true
        setState(c + acquires);
        return true;
    }
    //若是同步狀態等於0
    //在嘗試獲取同步狀態以前先調用writerShouldBlock()寫等待策略
    //ReentrantReadWriteLock中經過FairSync(公平鎖)和NonfairSync(非公品鎖)重寫writerShouldBlock()方法來達到公平與非公平的實現
    //NonfairSync(非公品鎖)中直接返回false表示不進行阻塞直接獲取
    //FairSync(公平鎖)中需調用hasQueuedPredecessors()方法判斷當前線程節點是否爲等待隊列的head結點的後置節點,是才能夠獲取鎖
    //獲取成功則將當前線程設置爲持有鎖線程,並返回true
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

其實寫鎖的實現與ReentrantLock差異不大,主要區別在於state的判斷,在獲取寫鎖的時候須要判斷讀鎖的狀態,而且

if (w == 0 || current != getExclusiveOwnerThread())
            return false;

這段代碼的邏輯也驗證了讀鎖不能升級寫鎖的緣由,當寫鎖個數不爲0,而且持有寫鎖的線程不是當前線程,直接返回false,阻塞了其餘線程搶佔寫鎖(固然包括持有讀鎖的線程)

寫鎖的釋放

public final boolean release(int arg) {
	if (tryRelease(arg)) {
	    Node h = head;
	    if (h != null && h.waitStatus != 0)
		unparkSuccessor(h);
	    return true;
	}
	return false;
}

 這裏寫鎖的釋放邏輯與ReentrantLock同樣,先tryRelease釋放鎖,直到寫鎖state等於0(全部重入鎖都釋放),喚醒後續節點線程

protected final boolean tryRelease(int releases) {
    //當前線程不是獲取了同步狀態的線程則拋出異常
    if (!isHeldExclusively())
	    throw new IllegalMonitorStateException();
    //釋放一次鎖,則將狀態-1
    int nextc = getState() - releases;
    //判斷寫狀態是否爲0,當寫鎖的全部重入都釋放完鎖後,狀態歸爲0
    boolean free = exclusiveCount(nextc) == 0;
    //若是釋放完成,將持有鎖的線程設置爲null
    if (free)
	    setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

寫鎖的讀取和釋放仍是很好理解的,尤爲針對比較熟悉ReentrantLock邏輯的人更好理解,可是讀鎖的實現相對來講就比較複雜了

讀鎖的獲取

讀鎖ReadLock的lock方法

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

 acquireShared方法實現以下,能夠看到讀鎖的lock方法是基於AQS共享鎖實現的

public final void acquireShared(int arg) {
	if (tryAcquireShared(arg) < 0)
	    doAcquireShared(arg);
	}
}

acquireShared方法的邏輯爲首先調用tryAcquireShared嘗試獲取寫鎖,若是獲取失敗(返回結果<0)則調用doAcquireShared阻塞該線程,tryAcquireShared方法與doAcquireShared方法咱們逐個分析

首先咱們須要先了解ReentrantReadWriteLock中的一些變量,方便咱們後續理解代碼

讀鎖的獲取相對比較複雜,由於讀鎖還有相似於獲取當前線程得到的鎖的個數等方法。

//用於記錄每一個線程獲取到的鎖的數量
//使用id和count記錄
static final class HoldCounter {
    int count = 0;
    final long tid = getThreadId(Thread.currentThread());
}
//這裏使用了ThreadLocal爲每一個線程都單獨維護了一個HoldCounter來記錄獲取的鎖的數量
static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

//獲取到的讀鎖的數量
private transient ThreadLocalHoldCounter readHolds;
//最後一次成功獲取到讀鎖的線程的HoldCounter對象
private transient HoldCounter cachedHoldCounter;
//第一個獲取到讀鎖的線程
private transient Thread firstReader = null;
//第一個獲取到讀鎖的線程擁有的讀鎖數量
private transient int firstReaderHoldCount;

上面的4個變量,其實就是完成一件事情,將獲取讀鎖的線程放入線程本地變量(ThreadLocal),方便從整個上 下文,根據當前線程獲取持有鎖的次數信息。其實 firstReader,firstReaderHoldCount ,cachedHoldCounter 這三個變量就是爲readHolds變量服務的,是一個優化手段,儘可能減小直接使用readHolds.get方法的次數,firstReader與firstReadHoldCount保存第一個獲取讀鎖的線程,也就是readHolds中並不會保存第一個獲取讀鎖的線程;cachedHoldCounter 緩存的是最後一個獲取線程的HolderCount信息,該變量主要是在若是當前線程屢次獲取讀鎖時,減小從readHolds中獲取HoldCounter的次數。

tryAcquireShared方法

protected final int tryAcquireShared(int unused) {
    //獲取當前線程
    Thread current = Thread.currentThread();
    //獲取同步狀態
    int c = getState();
    //若是已經有寫鎖被獲取而且獲取寫鎖的線程不是當前線程則獲取失敗
    //此處判斷邏輯隱含了一個條件,就是當有寫鎖獲取而且是獲取寫鎖的是當前線程,那麼不返回-1,容許此寫鎖獲取讀鎖
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    //獲取讀狀態(讀鎖被獲取的數量)
    int r = sharedCount(c);
    //根據是不是公平鎖來判斷是否須要進入阻塞,同時判斷當前讀鎖數量是否小於讀鎖容許最大數量(0xFFFF個),並進行一次CAS獲取
    //一個線程獲取也好,多個線程爭搶也好,if中的一次CAS獲取可以保證只有一個線程獲取到讀鎖
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        //若是是第一個獲取讀狀態的線程
        if (r == 0) {
            //設置firstReader和firstReaderHoldCount
            firstReader = current;
            firstReaderHoldCount = 1;
        //若是當前線程和第一個獲取讀鎖的線程是同一個線程那麼它的獲取的讀鎖數量加1(可見讀鎖也是一個重入鎖)
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        //是別的線程
        } else {
            //獲取最後一次獲取到讀狀態的線程
            HoldCounter rh = cachedHoldCounter;
            //rh == null(當前線程是第二個獲取的),或者當前線程和rh不是同一個
	    //那麼獲取到當前線程的HoldCounter並設置到cachedHoldCounter
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            //若是rh就是當前線程的HoldCounter而且當前線程獲取到的讀狀態位0那麼給當前線程的HoldCounter設置爲rh
            else if (rh.count == 0)
                readHolds.set(rh);
            //獲取到的讀鎖數加1
            rh.count++;
        }
        return 1;
    }
    //獲取讀鎖失敗調用該方法進行CAS循環獲取
    return fullTryAcquireShared(current);
}

fullTryAcquireShared方法的解讀

//第一次獲取讀鎖失敗,有兩種狀況:
//1)沒有寫鎖被佔用時,嘗試經過一次CAS去獲取鎖時,更新失敗(說明有其餘讀鎖在申請)
//2)當前線程佔有讀鎖,而且有其餘寫鎖在當前線程的下一個節點等待獲取寫鎖,最後在fullTryAcquireShared中獲取到讀鎖
//3)當前線程佔有寫鎖,而且有其餘寫鎖在當前線程的下一個節點等待獲取寫鎖,除非當前線程的下一個節點被取消,不然fullTryAcquireShared也獲取不到讀鎖fullTryAcquireShared也獲取不到讀鎖
final int fullTryAcquireShared(Thread current) {

    HoldCounter rh = null;
    //自旋
    for (;;) {
        int c = getState();
        //若是已經有寫鎖被獲取
        if (exclusiveCount(c) != 0) {
            //若是獲取寫鎖的線程不是當前線程則獲取失敗
            if (getExclusiveOwnerThread() != current)
                return -1;
            //若是獲取寫鎖的線程是當前線程則繼續保持這個寫鎖,而且這個寫鎖能夠在獲取讀鎖
        //若是此時應該進入阻塞
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                //第一次循環
                if (rh == null) {
		    //獲取最後一次獲取到讀狀態的線程
                    rh = cachedHoldCounter;
		    //rh == null(當前線程是第二個獲取的),或者當前線程和rh不是同一個
		    //那麼獲取到當前線程的HoldCounter並設置到cachedHoldCounter
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        //若是當前線程的讀鎖爲0就remove,由於後面會set
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                //不是第一次循環
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //嘗試CAS設置同步狀態
        //後續操做和tryAquireShared基本一致
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

readerShouldBlock阻塞判斷方法的解讀

/**
*  非公平鎖的讀鎖獲取策略
*/ 
final boolean readerShouldBlock() {
    //若是當前線程的後續節點爲獨佔式寫線程,則返回true(表示當前線程在tryAcquireShared方法中不能馬上獲取讀鎖,須要後續經過fullTryAcquireShared方法取判斷是否須要阻塞線程)
    //在fullTryAcquireShared方法中會經過判斷當前獲取讀鎖線程的讀鎖數量來判斷當前嘗試獲取讀鎖的線程是否持有寫鎖,若是持有寫鎖辨明所降級,須要將當前鎖降級的線程添加到阻塞隊列中從新獲取讀鎖
    //這麼作是爲了讓後續的寫線程有搶佔寫鎖的機會,不會由於一直有讀線程或者鎖降級狀況的存在而形成後續寫線程的飢餓等待
    return apparentlyFirstQueuedIsExclusive();
}

final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    return (h = head) != null &&
        (s = h.next)  != null &&
        !s.isShared()         &&
        s.thread != null;
}

/**
*  公平鎖的讀鎖獲取策略
*/  
final boolean readerShouldBlock() {
    //若是當前線程不是同步隊列頭結點的next節點(head.next) (判斷是否有前驅節點,若是有則返回false,不然返回true。遵循FIFO)
    //則阻塞當前線程 
    return hasQueuedPredecessors();
}

public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

doAcquireShared方法

/**
* 跟獨佔鎖很像,只不過共享鎖初始化時有傳入一個count,count爲1
*/
private void doAcquireShared(int arg) {
	//把當前線程封裝到一個SHARE類型Node中,添加到SyncQueue尾巴上
	final Node node = addWaiter(Node.SHARED);
	try {
	    boolean interrupted = false;
	    for (;;) {
		final Node p = node.predecessor();
		if (p == head) {//前繼節點是head節點,下一個就到本身了
		    int r = tryAcquireShared(arg);//非公平鎖實現,再嘗試獲取鎖
		    //state==0時tryAcquireShared會返回>=0(CountDownLatch中返回的是1)。state爲0說明共享次數已經到了,能夠獲取鎖了
		    //注意上面說的, 等於0表示不用喚醒後繼節點,大於0須要
		    if (r >= 0) {//r>0表示state==0,前繼節點已經釋放鎖,鎖的狀態爲可被獲取
			setHeadAndPropagate(node, r);//這一步設置node爲head節點設置node.waitStatus->Node.PROPAGATE,而後喚醒node.thread
			//喚醒head節點線程後,從這裏開始繼續往下走
			p.next = null; //head已經指向node節點,oldHead.next索引置空,方便p節點對象回收
			if (interrupted)
			    selfInterrupt();
			return;
		    }
		}
		//前繼節點非head節點,將前繼節點狀態設置爲SIGNAL,經過park掛起node節點的線程
		if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
		    interrupted = true;
	    }
	} catch (Throwable t) {
	    cancelAcquire(node);
	    throw t;
	}
}

setHeadAndPropagate方法

/**
 * 把node節點設置成head節點,且node.waitStatus->Node.PROPAGATE
 */
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head;//h用來保存舊的head節點
    setHead(node);//head引用指向node節點
    /* 這裏意思有兩種狀況是須要執行喚醒操做
     * 1.propagate > 0 表示調用方指明瞭後繼節點須要被喚醒
     * 2.頭節點後面的節點須要被喚醒(waitStatus<0),不管是老的頭結點仍是新的頭結點*/
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;	
        if (s == null || s.isShared())//node是最後一個節點或者 node的後繼節點是共享節點
	    /* 若是head節點狀態爲SIGNAL,喚醒head節點線程,重置head.waitStatus->0
	     * head節點狀態爲0(第一次添加時是0),設置head.waitStatus->Node.PROPAGATE表示狀態須要向後繼節點傳播
	     */
	    doReleaseShared();//對於這個方法,其實就是把node節點設置成Node.PROPAGATE狀態
    }
}

doReleaseShared方法

/** 
 * 把當前結點設置爲SIGNAL或者PROPAGATE
 * 喚醒head.next(B節點),B節點喚醒後能夠競爭鎖,成功後head->B,而後又會喚醒B.next,一直重複直到共享節點都喚醒
 * head節點狀態爲SIGNAL,重置head.waitStatus->0,喚醒head節點線程,喚醒後線程去競爭共享鎖
 * head節點狀態爲0,將head.waitStatus->Node.PROPAGATE傳播狀態,表示須要將狀態向後繼節點傳播
 */
private void doReleaseShared() {
	for (;;) {
		Node h = head;
		if (h != null && h != tail) {
			int ws = h.waitStatus;
			if (ws == Node.SIGNAL) {//head是SIGNAL狀態
			   /* head狀態是SIGNAL,重置head節點waitStatus爲0,這裏不直接設爲Node.PROPAGATE,
			    * 是由於unparkSuccessor(h)中,若是ws < 0會設置爲0,因此ws先設置爲0,再設置爲PROPAGATE
			    * 這裏須要控制併發,由於入口有setHeadAndPropagate跟release兩個,避免兩次unpark
			    */
			    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
				    continue;//設置失敗,從新循環
				    /* head狀態爲SIGNAL,且成功設置爲0以後,喚醒head.next節點線程
				     * 此時head、head.next的線程都喚醒了,head.next會去競爭鎖,成功後head會指向獲取鎖的節點,
				     * 也就是head發生了變化。看最底下一行代碼可知,head發生變化後會從新循環,繼續喚醒head的下一個節點
				     */
				    unparkSuccessor(h);
			/*
			 * 若是自己頭節點的waitStatus是出於重置狀態(waitStatus==0)的,將其設置爲「傳播」狀態。
			 * 意味着須要將狀態向後一個節點傳播
			 */
			} 
			else if (ws == 0 && !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
			    continue;
		}
		if (h == head)//若是head變了,從新循環
		break;
	}
}

讀鎖的釋放

releaseShared方法

public final boolean releaseShared(int arg) {
	//只有共享鎖徹底釋放,才能調用下面的doReleaseShared方法喚醒head節點的後繼節點
	//加入多個讀鎖同時在獲取到了讀鎖,即便不按順序釋放鎖,也不會影響head後繼節點的喚醒
	//由於共享鎖能夠有多個線程組成,可是釋放鎖的條件只有一個,就是讀鎖標記爲0,
	//即便最後釋放鎖的節點不是head,可是也能保證head後繼節點正常被喚醒
	if (tryReleaseShared(arg)) {
	    //此處的doReleaseShared方法與setHeadAndPropagate方法中鎖喚醒的節點有所差異
	    //setHeadAndPropagate方法只喚醒head後繼的共享鎖節點
	    //doReleaseShared方法則會喚醒head後繼的獨佔鎖或共享鎖
	    doReleaseShared();
	    return true;
	}
	return false;
}

 releaseShared方法首先會調用tryReleaseShared方法嘗試釋放共享鎖,成功的話會喚醒head節點的後繼節點

tryReleaseShared方法

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    //若是當前線程是第一個獲取讀鎖的線程
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        //若是第一個獲取讀鎖的線程只獲取了一個鎖那麼firstReader=null
        //不然firstReaderHoldCount--
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        //若是當前線程不是第一個獲取讀鎖的線程
        HoldCounter rh = cachedHoldCounter;
        //獲取當前線程的HoldCounter
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            //當前線程獲取的讀鎖小於等於1那麼就將remove當前線程的HoldCounter
            readHolds.remove();
            //當前線程獲取的讀鎖小於等於0拋出異常
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        //當前線程擁有的讀鎖數量減1
        --rh.count;
    }
    //自旋
    for (;;) {
        int c = getState();
        //釋放後的同步狀態
        int nextc = c - SHARED_UNIT;
        //CAS設置同步狀態,成功則返回是否同步狀態爲0
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

tryReleaseShared方法的邏輯在於將讀鎖的標記位-1,而且同時將firstReader和非firstReader的獲取鎖的次數-1,直到讀鎖標記位爲0時表示讀鎖釋放完畢

鎖降級

鎖降級指的是先獲取到寫鎖,而後獲取到讀鎖,而後釋放了寫鎖的過程。 
由於在獲取讀鎖的時候的判斷條件是:

if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;

因此當前線程是能夠在獲取了寫鎖的狀況下再去獲取讀鎖的。 
那麼在寫鎖釋放了以後應該還能繼續持有讀鎖。

最後:鎖是不支持鎖升級的(先獲取寫鎖,再獲取讀鎖而後釋放讀鎖), 
由於第一步獲取讀鎖的時候可能有多個線程獲取了讀鎖,這樣若是鎖升級的話將會致使寫操做對其餘已經獲取了讀鎖的線程不可見。
 

參考:

https://blog.csdn.net/LightOfMiracle/article/details/73184755

https://www.cnblogs.com/zaizhoumo/p/7782941.html

https://blog.csdn.net/u010577768/article/details/79995811

相關文章
相關標籤/搜索