Java并發(fā)編程——ReentrantReadWriteLock

在AQS的介紹中十气,鎖分為獨(dú)占鎖和共享鎖嫁佳,在上節(jié)中我們介紹了獨(dú)占鎖ReentrantLock纸淮,本次將針對(duì)另一個(gè)獨(dú)占鎖ReentrantReadWriteLock進(jìn)行學(xué)習(xí)允趟。

1. ReadWriteLock

ReadWriteLock屬于獨(dú)占鎖,它內(nèi)部包含一對(duì)相互關(guān)聯(lián)的Lock鎖码倦,分別用于讀寫操作。只要沒有 writer锭碳,讀取鎖可以由多個(gè) reader 線程同時(shí)保持袁稽。寫入鎖是獨(dú)占的。

read-write鎖相比互斥鎖提供了更大的并發(fā)量級(jí)別擒抛。雖然一次只有一個(gè)線程(writer 線程)可以修改共享數(shù)據(jù)推汽,但在許多情況下,任何數(shù)量的線程可以同時(shí)讀取共享數(shù)據(jù)(reader 線程)歹撒,讀-寫鎖利用了這一點(diǎn)。從理論上講暖夭,與互斥鎖相比,使用讀-寫鎖所允許的并發(fā)性增強(qiáng)將帶來更大的性能提高。在實(shí)踐中粪滤,只有在多處理器上并且只在訪問模式適用于共享數(shù)據(jù)時(shí),才能完全實(shí)現(xiàn)并發(fā)性增

相比于互斥鎖,使用read-write鎖提供了更強(qiáng)的并發(fā)能力。但是提升性能取決于讀寫操作期間讀取數(shù)據(jù)相對(duì)于修改數(shù)據(jù)的頻率,以及數(shù)據(jù)的爭用——即在同一時(shí)間試圖對(duì)該數(shù)據(jù)執(zhí)行讀取或?qū)懭氩僮鞯木€程數(shù)行拢。例如祖秒,某個(gè)最初用數(shù)據(jù)填充并且之后不經(jīng)常對(duì)其進(jìn)行修改的 collection,因?yàn)榻?jīng)常對(duì)其進(jìn)行搜索(比如搜索某種目錄)舟奠,所以這樣的 collection 是使用讀-寫鎖的理想候選者竭缝。但是,如果數(shù)據(jù)更新變得頻繁沼瘫,數(shù)據(jù)在大部分時(shí)間都被獨(dú)占鎖抬纸,這時(shí),就算存在并發(fā)性增強(qiáng)耿戚,也是微不足道的湿故。更進(jìn)一步地說,如果讀取操作所用時(shí)間太短膜蛔,則讀-寫鎖實(shí)現(xiàn)(它本身就比互斥鎖復(fù)雜)的開銷將成為主要的執(zhí)行成本坛猪,在許多讀-寫鎖實(shí)現(xiàn)仍然通過一小段代碼將所有線程序列化時(shí)更是如此。最終皂股,只有通過分析和測量墅茉,才能確定應(yīng)用程序是否適合使用讀-寫鎖。

所以根據(jù)上面推斷呜呐,讀寫鎖適用于讀取較多就斤、寫比較少的場景。

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading.
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing.
     */
    Lock writeLock();
}

ReadWriteLock提供了兩個(gè)方法分別用于獲取Read-Lock和Write-Lock蘑辑。

2. ReentrantReadWriteLock特點(diǎn)

ReentrantReadWriteLock與ReentrantLock實(shí)現(xiàn)非常類似洋机。它具有以下屬性。

1以躯、鎖獲取順序
ReentrantLock不會(huì)將讀取者優(yōu)先或?qū)懭胝邇?yōu)先強(qiáng)加給鎖訪問的排序槐秧。但是,它確實(shí)支持可選的公平 策略忧设。

2、默認(rèn)模式颠通,非公平模式
當(dāng)非公平策略(默認(rèn))構(gòu)造時(shí)址晕,未指定進(jìn)入讀寫鎖的順序,受到 reentrancy 約束的限制顿锰。連續(xù)競爭的非公平鎖可能無限期地推遲一個(gè)或多個(gè) reader 或 writer 線程谨垃,但吞吐量通常要高于公平鎖启搂。

3、公平模式
當(dāng)使用公平策略是刘陶,線程使用一種近似同步到達(dá)的順序爭奪資源胳赌。當(dāng)線程釋放當(dāng)前持有鎖時(shí),等待時(shí)間最長的write線程獲取到寫入鎖匙隔,如果有一組等待時(shí)間大于所有正在等待的 writer 線程 的 reader 線程疑苫,將為該組分配寫入鎖。

當(dāng)一個(gè)線程嘗試獲取公平策略的read-lock時(shí)纷责,如果write-lock被持有或者有等待的write線程捍掺,則當(dāng)前線程會(huì)阻塞。直到當(dāng)前最舊的等待 writer 線程已獲得并釋放了寫入鎖之后再膳,該線程才會(huì)獲得讀取鎖挺勿。當(dāng)然,如果等待 writer 放棄其等待喂柒,而保留一個(gè)或更多 reader 線程為隊(duì)列中帶有寫入鎖自由的時(shí)間最長的 waiter不瓶,則將為那些 reader 分配讀取鎖。

4灾杰、鎖降級(jí)
重入還允許從寫入鎖降級(jí)為讀取鎖蚊丐,其實(shí)現(xiàn)方式是:先獲取寫入鎖,然后獲取讀取鎖吭露,最后釋放寫入鎖吠撮。但是,從讀取鎖升級(jí)到寫入鎖是不可能的讲竿。

 class CachedData {
   Object data;
   volatile boolean cacheValid;
   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        // Recheck state because another thread might have acquired
        //   write lock and changed state before we did.
        if (!cacheValid) {
          data = ...
          cacheValid = true;
        }
        // Downgrade by acquiring read lock before releasing write lock
        rwl.readLock().lock();
        rwl.writeLock().unlock(); // Unlock write, still hold read
     }

     use(data);
     rwl.readLock().unlock();
   }
 }

5泥兰、鎖獲取的中斷
讀取鎖和寫入鎖都支持鎖獲取期間的中斷。

6题禀、Condition 支持
寫入鎖提供了一個(gè) Condition 實(shí)現(xiàn)鞋诗,對(duì)于寫入鎖來說,該實(shí)現(xiàn)的行為與 ReentrantLock.newCondition() 提供的 Condition 實(shí)現(xiàn)對(duì) ReentrantLock 所做的行為相同迈嘹。當(dāng)然削彬,此 Condition 只能用于寫入鎖。

讀取鎖不支持 Condition秀仲,readLock().newCondition() 會(huì)拋出 UnsupportedOperationException融痛。

在使用某些種類的 Collection 時(shí),可以使用 ReentrantReadWriteLock 來提高并發(fā)性神僵。通常雁刷,在預(yù)期 collection 很大,讀取者線程訪問它的次數(shù)多于寫入者線程保礼,并且 entail 操作的開銷高于同步開銷時(shí)沛励,這很值得一試责语。例如,以下是一個(gè)使用 TreeMap 的類目派,預(yù)期它很大坤候,并且能被同時(shí)訪問。

class RWDictionary {
    private final Map<String, Data> m = new TreeMap<String, Data>();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Data get(String key) {
        r.lock();
        try { return m.get(key); }
        finally { r.unlock(); }
    }
    public String[] allKeys() {
        r.lock();
        try { return m.keySet().toArray(); }
        finally { r.unlock(); }
    }
    public Data put(String key, Data value) {
        w.lock();
        try { return m.put(key, value); }
        finally { w.unlock(); }
    }
    public void clear() {
        w.lock();
        try { m.clear(); }
        finally { w.unlock(); }
    }
 }

實(shí)現(xiàn)注意事項(xiàng)
此鎖最多支持 65535 個(gè)遞歸寫入鎖和 65535 個(gè)讀取鎖企蹭。試圖超出這些限制將導(dǎo)致鎖方法拋出 Error白筹。

3. ReentrantReadWriteLock源碼分析

3.1 基本框架結(jié)構(gòu)組成
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    final Sync sync;

    /**
     * Creates a new {@code ReentrantReadWriteLock} with
     * default (nonfair) ordering properties.
     */
    public ReentrantReadWriteLock() {
        this(false);
    }

    /**
     * Creates a new {@code ReentrantReadWriteLock} with
     * the given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    .....
}

ReentrantReadWriteLock內(nèi)部由一個(gè)ReadLock對(duì)象和一個(gè)WriteLock對(duì)象組成,分別是由內(nèi)部類實(shí)現(xiàn)练对。同時(shí)包含實(shí)現(xiàn)同步功能的Sync對(duì)象遍蟋。在構(gòu)造方法總,支持無參構(gòu)造函數(shù)和有參構(gòu)造函數(shù)螟凭,默認(rèn)是采用非公平策略虚青。所以根據(jù)構(gòu)造函數(shù)中FairSync、NonfairSync螺男、ReadLock和WriteLock這四個(gè)類的實(shí)現(xiàn)棒厘。

因?yàn)槭褂玫腣isual Studio繪制的UML圖,沒發(fā)現(xiàn)內(nèi)部類嵌套的繪制關(guān)系圖標(biāo)下隧,所以使用組合替代了奢人。


ReentrantReadWriteLock類圖.png
  1. ReentrantReadWriteLock實(shí)現(xiàn)了ReadWriteLock接口。ReadWriteLock是一個(gè)讀寫鎖的接口淆院,提供了"獲取讀鎖的readLock()函數(shù)" 和 "獲取寫鎖的writeLock()函數(shù)"何乎。
  2. ReentrantReadWriteLock中包含:sync對(duì)象,讀鎖readerLock和寫鎖writerLock土辩。讀鎖ReadLock和寫鎖WriteLock都實(shí)現(xiàn)了Lock接口支救。讀鎖ReadLock和寫鎖WriteLock中也都分別包含了"Sync對(duì)象",它們的Sync對(duì)象和ReentrantReadWriteLock的Sync對(duì)象 是一樣的拷淘,就是通過sync各墨,讀鎖和寫鎖實(shí)現(xiàn)了對(duì)同一個(gè)對(duì)象的訪問。
  3. 和"ReentrantLock"一樣启涯,sync是Sync類型贬堵;而且,Sync也是一個(gè)繼承于AQS的抽象類结洼。Sync也包括"公平鎖"FairSync和"非公平鎖"NonfairSync黎做。sync對(duì)象是"FairSync"和"NonfairSync"中的一個(gè),默認(rèn)是"NonfairSync"松忍。
3.2 Sync類的源碼

通過上面的UML類圖關(guān)系可以看出引几,最終ReadLock和WriteLock執(zhí)行的本質(zhì)方法都是在Sync類中。

/**
 * ReentrantReadWriteLock內(nèi)部的實(shí)現(xiàn)機(jī)制
 * @author Iflytek_dsw
 *
 */
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 6317671515068378041L;

    /**
     * 這些量用來計(jì)算Read和Write鎖的數(shù)量挽铁,ReentrantReadWriterLock使用一個(gè)32位的int類型來表示鎖被占用的線程數(shù)
     * (ReentrantLock中的state)采取的辦法是:
     * 高16位用來表示讀鎖(共享鎖)占有的線程數(shù)量伟桅,用低16位表示寫鎖(獨(dú)占鎖)被占用的數(shù)量
     */
    static final int SHARED_SHIFT   = 16;
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

    /**
     * 通過進(jìn)行右移16位計(jì)算出共享鎖(讀取鎖)的占用個(gè)數(shù)
     */
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    /**
     * 通過進(jìn)行&運(yùn)算計(jì)算出低16位的寫鎖(獨(dú)占鎖)個(gè)數(shù)
     */
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

    /**
     * 定義一個(gè)容器來計(jì)算保存每個(gè)線程read鎖的個(gè)數(shù)
     */
    static final class HoldCounter {
        int count = 0;
        // Use id, not reference, to avoid garbage retention
        final long tid = Thread.currentThread().getId();
    }

    /**
     * ThreadLocal的使用來針對(duì)每個(gè)線程進(jìn)行存儲(chǔ)HoldCounter 
     */
    static final class ThreadLocalHoldCounter
        extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }

    /**
     * 一個(gè)read線程的HoldCounter,當(dāng)為0的時(shí)候進(jìn)行刪除叽掘。
     */
    private transient ThreadLocalHoldCounter readHolds;

    /**
     *  cachedHoldCounter 緩存的是最后一個(gè)獲取線程的HolderCount信息楣铁,
     *  該變量主要是在如果當(dāng)前線程多次獲取讀鎖時(shí),減少從readHolds中獲取HoldCounter的次數(shù)
     */
    private transient HoldCounter cachedHoldCounter;

    /**
     * firstReader is the first thread to have acquired the read lock.
     * firstReaderHoldCount is firstReader's hold count.
     */
    private transient Thread firstReader = null;
    private transient int firstReaderHoldCount;

    Sync() {
        readHolds = new ThreadLocalHoldCounter();
        setState(getState()); // ensures visibility of readHolds
    }

    /**
     * Returns true if the current thread, when trying to acquire
     * the read lock, and otherwise eligible to do so, should block
     * because of policy for overtaking other waiting threads.
     */
    abstract boolean readerShouldBlock();

    /**
     * Returns true if the current thread, when trying to acquire
     * the write lock, and otherwise eligible to do so, should block
     * because of policy for overtaking other waiting threads.
     */
    abstract boolean writerShouldBlock();

    protected final boolean tryRelease(int releases) {
        /**當(dāng)前線程不支持鎖的時(shí)候更扁,拋IllegalMonitor異常*/
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        int nextc = getState() - releases;
        /**當(dāng)前鎖線程的個(gè)數(shù)是否為0個(gè)*/
        boolean free = exclusiveCount(nextc) == 0;
        if (free)
            setExclusiveOwnerThread(null);
        setState(nextc);
        return free;
    }

    protected final boolean tryAcquire(int acquires) {
        //省略
    }

    protected final boolean tryReleaseShared(int unused) {
        //省略
    }

    private IllegalMonitorStateException unmatchedUnlockException() {
        return new IllegalMonitorStateException(
            "attempt to unlock read lock, not locked by current thread");
    }

    protected final int tryAcquireShared(int unused) {
        //省略
    }

    /**
     * Full version of acquire for reads, that handles CAS misses
     * and reentrant reads not dealt with in tryAcquireShared.
     */
    final int fullTryAcquireShared(Thread current) {
        //省略
    }

    /**
     * Performs tryLock for write, enabling barging in both modes.
     * This is identical in effect to tryAcquire except for lack
     * of calls to writerShouldBlock.
     */
    final boolean tryWriteLock() {
        //省略
    }

    /**
     * Performs tryLock for read, enabling barging in both modes.
     * This is identical in effect to tryAcquireShared except for
     * lack of calls to readerShouldBlock.
     */
    final boolean tryReadLock() 
        //省略
    }

    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    // Methods relayed to outer class

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

    final Thread getOwner() {
        // Must read state before owner to ensure memory consistency
        return ((exclusiveCount(getState()) == 0) ?
                null :
                getExclusiveOwnerThread());
    }

    final int getReadLockCount() {
        return sharedCount(getState());
    }

    final boolean isWriteLocked() {
        return exclusiveCount(getState()) != 0;
    }

    final int getWriteHoldCount() {
        return isHeldExclusively() ? exclusiveCount(getState()) : 0;
    }

    final int getReadHoldCount() {
        //省略
    }

    /**
     * Reconstitute this lock instance from a stream
     * @param s the stream
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        readHolds = new ThreadLocalHoldCounter();
        setState(0); // reset to unlocked state
    }

    final int getCount() { return getState(); }
}

上面的代碼中省略多個(gè)關(guān)鍵方法的代碼實(shí)現(xiàn)盖腕,代碼比較多。后面在具體的流程中會(huì)單獨(dú)挑出來分析浓镜。這里我們只要知道在Sync類中定義了如下關(guān)鍵方法:

  • readerShouldBlock()
  • writerShouldBlock()
  • tryAcquire(int acquires)
  • tryReleaseShared(int unused)
  • tryAcquireShared(int unused)
  • tryWriteLock()
  • tryReadLock()
3.3 FairSync公平鎖

ReentrantReadWriteLock同樣支持公平鎖和非公平鎖溃列。FairSync的實(shí)現(xiàn)如下:

static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}

在Sync類中預(yù)留了writerShouldBlock()和readerShouldBlock()兩個(gè)抽象方法供子類重寫。在FairSync中返回的是hasQueuedPredecessors()方法的返回值膛薛。

/**
 * 當(dāng)前線程前面是否有處理的線程听隐,如果有返回true,反之返回false哄啄。
 * 隊(duì)列為空同樣返回false
 */
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());
}
3.4 NonfairSync非公平鎖
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    final boolean writerShouldBlock() {
        return false; // writers can always barge
    }
    final boolean readerShouldBlock() {
        /* As a heuristic to avoid indefinite writer starvation,
         * block if the thread that momentarily appears to be head
         * of queue, if one exists, is a waiting writer.  This is
         * only a probabilistic effect since a new reader will not
         * block if there is a waiting writer behind other enabled
         * readers that have not yet drained from the queue.
         */
        return apparentlyFirstQueuedIsExclusive();
    }
}

在非公平鎖中雅任,write返回的是false。而在讀鎖中則返回apparentlyFirstQueuedIsExclusive()方法咨跌。

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

該方法如果頭節(jié)點(diǎn)不為空沪么,并頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)不為空,并且不是共享模式【獨(dú)占模式锌半,寫鎖】禽车、并且線程不為空,則返回true刊殉。

這個(gè)方法判斷隊(duì)列的head.next是否正在等待獨(dú)占鎖(寫鎖殉摔,因?yàn)樵赗eentrantReadWriteLock中讀寫鎖共用一個(gè)隊(duì)列)。當(dāng)然這個(gè)方法執(zhí)行的過程中隊(duì)列的形態(tài)可能發(fā)生變化冗澈。這個(gè)方法的意思是:讀鎖不應(yīng)該讓寫鎖始終等待钦勘,因?yàn)樵谕粫r(shí)刻讀寫鎖只能有一種鎖在“工作”。

3.5 ReadLock源碼解讀

ReadLock是讀取鎖的獲取亚亲。

public static class ReadLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -5992448646407690164L;
    private final Sync sync;

    /**
     * Constructor for use by subclasses
     *
     * @param lock the outer lock object
     * @throws NullPointerException if the lock is null
     */
    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    /**
     * Acquires the read lock.
     * 申請read鎖
     * 如果write鎖沒有被別的線程持有彻采,則立即返回read鎖。如果write鎖被占用捌归,則當(dāng)前線程會(huì)被阻塞肛响,直至獲取到read鎖。
     */
    public void lock() {
        sync.acquireShared(1);
    }


    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    /**
     * Acquires the read lock only if the write lock is not held by
     * another thread at the time of invocation.
     *
     * <p>If the write lock is held by another thread then
     * this method will return immediately with the value
     * {@code false}.
     *
     * @return {@code true} if the read lock was acquired
     */
    public boolean tryLock() {
        return sync.tryReadLock();
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    /**
     * Attempts to release this lock.
     *
     * <p> If the number of readers is now zero then the lock
     * is made available for write lock attempts.
     */
    public  void unlock() {
        sync.releaseShared(1);
    }

    /**
     * Throws {@code UnsupportedOperationException} because
     * {@code ReadLocks} do not support conditions.
     *
     * @throws UnsupportedOperationException always
     */
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }

    /**
     * Returns a string identifying this lock, as well as its lock state.
     * The state, in brackets, includes the String {@code "Read locks ="}
     * followed by the number of held read locks.
     *
     * @return a string identifying this lock, as well as its lock state
     */
    public String toString() {
        int r = sync.getReadLockCount();
        return super.toString() +
            "[Read locks = " + r + "]";
    }
}

ReadLock繼承Lock類惜索,并實(shí)現(xiàn)了對(duì)應(yīng)的申請所特笋、釋放鎖方法。

3.5.1 ReadLock申請鎖lock
public void lock() {
    sync.acquireShared(1);
}

read鎖是共享鎖巾兆,這里調(diào)用sync對(duì)象的acquireShared()方法來申請鎖猎物。

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

這里tryAcquireShared方法執(zhí)行就到了Sync類中虎囚。

protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. 如果write鎖被其它線程占用,申請fail
     * 2. Otherwise, this thread is eligible for
     *    lock wrt state, so ask if it should block
     *    because of queue policy. If not, try
     *    to grant by CASing state and updating count.
     *    Note that step does not check for reentrant
     *    acquires, which is postponed to full version
     *    to avoid having to check hold count in
     *    the more typical non-reentrant case.
     * 3. If step 2 fails either because thread
     *    apparently not eligible or CAS fails or count
     *    saturated, chain to version with full retry loop.
     */
    /**獲取當(dāng)前的線程*/
    Thread current = Thread.currentThread();
    /**獲取當(dāng)前鎖的狀態(tài)值*/
    int c = getState();
    /**如果寫鎖(獨(dú)占鎖)占有個(gè)數(shù)不為0蔫磨,并且持有線程不等于當(dāng)前線程淘讥,返回-1*/
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    /**獲取讀取鎖(共享鎖)的個(gè)數(shù)*/
    int r = sharedCount(c);
    /** 如果當(dāng)前read鎖不被阻塞,并且個(gè)數(shù)小于最大MAX_COUNT,同時(shí)CAS操作成功*/
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {//如果當(dāng)前共享鎖個(gè)數(shù)為0,沒有被持有
            /**當(dāng)前線程賦值給firstReader,并且firstReadHoldCount賦值為1*/
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            /**如果當(dāng)前線程已經(jīng)持有所膛锭,則firstReaderHoldCount自加1*/
            firstReaderHoldCount++;
        } else {/**其它情況則是新來的一個(gè)線程*/
            /**新建一個(gè)HoldCounter對(duì)象存儲(chǔ)cachedHoldCounter*/
            HoldCounter rh = cachedHoldCounter;
            /**如果cachedHoldCounter為空,或tid不等于當(dāng)前線程*/
            if (rh == null || rh.tid != current.getId())
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    /**如果鎖被占用等不滿足上述情況蝗岖,則通過fullTryAcquireShared進(jìn)行申請*/
    return fullTryAcquireShared(current);
}

在tryAcquireShared方法中, 如果當(dāng)前read鎖不被阻塞榔至,并且個(gè)數(shù)小于最大MAX_COUNT,同時(shí)CAS操作成功則申請鎖成功抵赢。反之通過fullTryAcquireShared方法進(jìn)行申請。

final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    /**采用自旋的方式洛退,直至申請到鎖*/
    for (;;) {
        int c = getState();
        /**如果Write鎖被占用瓣俯,并且持有線程不是當(dāng)前線程則返回-1*/
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {//Write鎖被持有
            // 如果最近沒有申請過read鎖
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {//沒有申請過
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != current.getId()) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        /**Read鎖已經(jīng)到達(dá)最大值*/
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        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 != current.getId())
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

采用自旋的方式,其它邏輯同tryAcquireShared類似兵怯。

  • 如果Write鎖被占有彩匕,并且持有寫鎖的線程不是當(dāng)前線程直接返回-1
  • 如果Write鎖當(dāng)前線程持有,并且在CLH隊(duì)列中下一個(gè)節(jié)點(diǎn)是Write鎖媒区,則返回-1

最后在所有申請失敗返回-1的時(shí)候驼仪,則通過doAcquireShared()方法進(jìn)行申請。

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

通過上面的分析ReadLock的lock執(zhí)行流程如下:
[圖片上傳失敗...(image-c16b-1528795162670)]

3.5.2 tryLock()嘗試申請鎖

前面在ReentrantLock的筆記中袜漩,同樣有tryLock方法绪爸。其實(shí)它們都差不多。

public  boolean tryLock() {
    return sync.tryReadLock();
}

tryLock的內(nèi)部調(diào)用的Sync類的tryReadLock()方法宙攻。

final boolean tryReadLock() {
    Thread current = Thread.currentThread();
    for (;;) {
        int c = getState();
        /**Write鎖被占用并且占用線程不是當(dāng)前線程奠货,返回false*/
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            return false;
        /**返回read鎖的個(gè)數(shù)*/
        int r = sharedCount(c);
        /**如果是最大值,則拋出異常*/
        if (r == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (r == 0) {//Read的個(gè)數(shù)為0座掘,則將當(dāng)前線程賦值給firstReader递惋,并且firstReaderHoldCount賦值為1
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {//如果firstReader==當(dāng)前線程,則firstReaderHoldCount自加1
                firstReaderHoldCount++;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != current.getId())
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }
            return true;
        }
    }
}
3.5.3 鎖的釋放
public  void unlock() {
    sync.releaseShared(1);
}

unlock釋放鎖最終還是在Sync類的releaseShared方法中進(jìn)行釋放溢陪。releaseShared方法是在AQS類中萍虽。

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

在AQS中,我們知道releaseShared方法是AQS預(yù)留給子類進(jìn)行實(shí)現(xiàn)的形真,所以最終的實(shí)現(xiàn)還是在Sync類中杉编。

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != current.getId())
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0;
    }
}

4 總結(jié)

上面我們針對(duì)ReadLock的鎖的申請、釋放進(jìn)行了分析,WriteLock由于是個(gè)獨(dú)占鎖邓馒,大致跟ReentrantLock流程差不多嘶朱,不做過多分析∪蘧唬總體說來见咒,還是需要多讀幾遍源碼才能理解透徹。
最后給一個(gè)使用的示例:

public class MyTest {

    static StudentList studentList = new StudentList();
    public static void main(String[]args){
        for(int i=0;i<3;i++){
            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    studentList.addItem();
                }
            }).start();
        }
        
        for(int i=0;i<3;i++){
            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    studentList.printList();
                }
            }).start();
        }
    }
}

class StudentList{
    private List<Integer> listNumber = new ArrayList<Integer>();
    
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private ReadLock readLock;
    private WriteLock writeLock;
    public StudentList(){
        readLock = reentrantReadWriteLock.readLock();
        writeLock = reentrantReadWriteLock.writeLock();
    }
    
    public void printList(){
        readLock.lock();
        for(int i=0;i<listNumber.size();i++){
            System.out.println(Thread.currentThread().getName() + "--printList--current:" + i);
        }
        readLock.unlock();
    }
    
    public void addItem(){
        writeLock.lock();
        for(int i=listNumber.size();i<5;i++){
            System.out.println(Thread.currentThread().getName() + "--addItem--current:" + i);
            listNumber.add(i);
        }
        writeLock.unlock();
    }
}

執(zhí)行結(jié)果:

Thread-0--addItem--current:0
Thread-0--addItem--current:1
Thread-0--addItem--current:2
Thread-0--addItem--current:3
Thread-0--addItem--current:4
Thread-3--printList--current:0
Thread-3--printList--current:1
Thread-3--printList--current:2
Thread-3--printList--current:3
Thread-3--printList--current:4
Thread-4--printList--current:0
Thread-5--printList--current:0
Thread-4--printList--current:1
Thread-5--printList--current:1
Thread-4--printList--current:2
Thread-5--printList--current:2
Thread-4--printList--current:3
Thread-5--printList--current:3
Thread-4--printList--current:4
Thread-5--printList--current:4

通過運(yùn)行結(jié)果我們可以證明一個(gè)結(jié)論:Read鎖是共享鎖挂疆,每個(gè)線程都能獲取到。Write鎖是獨(dú)占鎖下翎,只能同時(shí)被一個(gè)線程持有缤言。

當(dāng)我們把Write鎖釋放代碼注釋:

public void addItem(){
    writeLock.lock();
    for(int i=listNumber.size();i<5;i++){
        System.out.println(Thread.currentThread().getName() + "--addItem--current:" + i);
        listNumber.add(i);
    }
    //writeLock.unlock();
}

Thread-0--addItem--current:0
Thread-0--addItem--current:1
Thread-0--addItem--current:2
Thread-0--addItem--current:3
Thread-0--addItem--current:4

通過運(yùn)行結(jié)果可以有結(jié)論:當(dāng)Write鎖被持有的時(shí)候,Read鎖是無法被其它線程申請的视事,會(huì)處于阻塞狀態(tài)胆萧。直至Write鎖釋放。同時(shí)也可以驗(yàn)證到當(dāng)同一個(gè)線程持有Write鎖時(shí)是可以申請到Read鎖俐东。

推薦閱讀:
JDK1.8源碼分析之ReentrantReadWriteLock(七):https://www.cnblogs.com/leesf456/p/5419132.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末跌穗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子虏辫,更是在濱河造成了極大的恐慌蚌吸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砌庄,死亡現(xiàn)場離奇詭異羹唠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)娄昆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門佩微,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萌焰,你說我怎么就攤上這事哺眯。” “怎么了扒俯?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵奶卓,是天一觀的道長。 經(jīng)常有香客問我陵珍,道長寝杖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任互纯,我火速辦了婚禮瑟幕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己只盹,他們只是感情好辣往,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著殖卑,像睡著了一般站削。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上孵稽,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天许起,我揣著相機(jī)與錄音,去河邊找鬼菩鲜。 笑死园细,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的接校。 我是一名探鬼主播猛频,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蛛勉!你這毒婦竟也來了鹿寻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤诽凌,失蹤者是張志新(化名)和其女友劉穎毡熏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皿淋,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡招刹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窝趣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疯暑。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖哑舒,靈堂內(nèi)的尸體忽然破棺而出妇拯,到底是詐尸還是另有隱情,我是刑警寧澤洗鸵,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布越锈,位于F島的核電站,受9級(jí)特大地震影響膘滨,放射性物質(zhì)發(fā)生泄漏甘凭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一火邓、第九天 我趴在偏房一處隱蔽的房頂上張望丹弱。 院中可真熱鬧德撬,春花似錦、人聲如沸躲胳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坯苹。三九已至隆檀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粹湃,已是汗流浹背恐仑。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留再芋,地道東北人菊霜。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像济赎,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子记某,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • JAVA并發(fā)編程與高并發(fā)解決方案 - 并發(fā)編程 四 相關(guān)文章 JAVA并發(fā)編程與高并發(fā)解決方案 - 并發(fā)編程 一 ...
    chuIllusions丶閱讀 2,211評(píng)論 0 6
  • 一司训、前言 借用Java并發(fā)編程實(shí)踐中的話"編寫正確的程序并不容易,而編寫正常的并發(fā)程序就更難了"液南,相比于順序執(zhí)行的...
    運(yùn)維開發(fā)筆記閱讀 329評(píng)論 0 2
  • 作者: 一字馬胡 轉(zhuǎn)載標(biāo)志 【2017-11-03】 更新日志 前言 在java中壳猜,鎖是實(shí)現(xiàn)并發(fā)的關(guān)鍵組件,多個(gè)...
    一字馬胡閱讀 44,144評(píng)論 1 32
  • ReadWriteLock 從這一節(jié)開始介紹鎖里面的最后一個(gè)工具:讀寫鎖(ReadWriteLock)滑凉。 Reen...
    raincoffee閱讀 637評(píng)論 0 1
  • 20160403桜舞う4月の教室で在這櫻花飛舞的四月的教室中波打つ胸をはずませながら 心潮澎湃出會(huì)った永遠(yuǎn)(とわ)...
    閑謝閱讀 212評(píng)論 0 1