ReentrantReadWriteLock読み取り/書き込みロック分析

ReadWriteLockの概要

前に述べたように、ReentrantLockは排他的ロックであり、一度に1つのスレッドのみがロックを取得できます。実際、読み取りの書き込みが増えるシナリオが多く、読み取り操作自体にデータ競合の問題が発生することはありません。排他ロックを使用すると、リーダースレッドの1つが他のリーダースレッドを待機し、パフォーマンスが低下する可能性があります。

読み取りを増やして書き込みを減らすというこのシナリオに対応して、読み取り/書き込みロックが発生しました。読み取り/書き込みロックにより、複数のリーダースレッドが同時にアクセスできますが、ライタースレッドがアクセスすると、すべてのリーダースレッドと他のライタースレッドがブロックされます。Javaの読み取り/書き込みロックのトップレベルインターフェイスを見てみましょう。これは、java.util.concurrent.locksパッケージにあります。

public interface ReadWriteLock {
    // 读锁
    Lock readLock();
	// 写锁
    Lock writeLock();
}

読み取り/書き込みロックが実際には1つの書き込みロックと1つの読み取りロックのペアを維持していることをすぐに理解できると思います。読み取り/書き込み分離の戦略により、複数のスレッドが同時に読み取りロックを取得できます。 、同時実行性が大幅に向上します。

読み取り/書き込みロックケース

JavaDocドキュメントは非常に詳細で、ReentrantReadWriteLockの使用例を示しています。直接見てみましょう。

class CachedData {
    Object data;
    volatile boolean cacheValid;
    // 创建读写锁实例
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    void processCachedData() {
        // 获取读锁
        rwl.readLock().lock();
        // 缓存失效的情况
        if (!cacheValid) { 
            
            // 释放掉读锁,必须!在获取写锁之前给读锁释放了
            rwl.readLock().unlock();
            // 获取写锁
            rwl.writeLock().lock();

            try {
                // 重新检查状态,因为在等待写锁的过程中,可能前面有其他写线程执行过了
                if (!cacheValid) { 
                    data = ...
                    cacheValid = true;
                }
                // 持有写锁的情况下,获取读锁的,称为 “锁降级”
                rwl.readLock().lock();
            } finally {
                // 释放写锁,此时还剩一个读锁
                rwl.writeLock().unlock(); 
            }
        }

        try {
            use(data);
        } finally {
            // 释放读锁
            rwl.readLock().unlock();
        }
    }
}

次の分析部分で、少し詳しく要約します。

ReentrantReadWriteLock読み取り/書き込みロックは、読み取りロックと書き込みロックに分けられます。読み取りロックは共有ロックであり、書き込みロックは排他ロックです。書き込みロックを保持しているスレッドは、ロック劣化と呼ばれる読み取りロックを取得し続けることができます。

ReentrantReadWriteLockアーキテクチャの概要

ReentrantReadWriteLockは、ReadWriteLockの実装です。実際、reentrant read-write lockという名前を見ると、おそらくその意味を推測できます。readLock()メソッドとwriteLock()メソッドの実装に加えて、後で分析するいくつかの重要なメソッドも提供します。

ReentrantReadWriteLock読み取り/書き込みロック分析

 

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** 内部维护ReadLock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** 内部维护WriteL */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** 读、写锁公用一个AQS的Sync的实例 */
    final Sync sync;
    
	/** 默认使用非公平模式 */
    public ReentrantReadWriteLock() {
        this(false);
    }
    /** 初始化读锁和写锁实例 */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

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

    /**
     * AQS的实现
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {   
        // ...
    }
    
     /**
     * Sync 非公平版本的实现
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }

    /**
     * Sync 公平版本的实现
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

    /**
     * 可以通过ReentrantReadWriteLock#readLock方法得到一个读锁实例
     */
    public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        private final Sync sync;
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
        // 可以看到读锁使用的是共享模式
        public void lock() {
            sync.acquireShared(1);
        }
        public void unlock() {
            sync.releaseShared(1);
        }
        //...省略tryLock、lockInterruptibly等方法
    }

    /**
     * 可以通过ReentrantReadWriteLock#writeLock方法获得一个写锁实例
     */
    public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
        private final Sync sync;
        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
        // 可以看到读锁使用的是独占模式
        public void lock() {
            sync.acquire(1);
        }
        public void unlock() {
            sync.release(1);
        }
        //...省略tryLock、lockInterruptibly等方法
    }

要約しましょう:

  • ReentrantReadWriteLockは、ReadLockとWriteLockの2つの内部クラスを維持します。どちらも、Syncに特定の機能の実装を委託します[SyncはAQSの実装であり、前に非常に明確です]。
  • ReentrantLockと同様に、公平性と不公平性の2つの実装(FairSyncとNonfairSync)も提供します。これらはSyncの実装クラスです。2つの違いについては、「公平戦略と不公平戦略の違い」を参照してください。
  • ReadLockインスタンスとWriteLockインスタンスは、readLock()とwriteLock()の2つのメソッドを介して取得できます。
  • ReadLockは共有モードを使用し、WriteLockは排他モードを使用します。2つの違いについては、以下を参照してください。Java並行パッケージソースコード学習シリーズ:AQS共有および排他的なリソースの取得と解放の違い。

重要なフィールドと内部クラス表現を同期する

AQSの学習時に述べたように、AQSのキーは同期状態フィールドの状態です。たとえば、ReentrantLockを例にとると、その状態は0でロックが解放されていることを示し、1でロックが取得されていることを示します。ロックが同じスレッドによって使用されていることを示すために1より大きい。再入力。

ただし、読み取り/書き込みロックは2つの状態を維持する必要があることが知られていますが、1つの整数変数状態のみでそれを表すにはどうすればよいですか?読み取り/書き込みロックは、ビット単位のカットのアイデアを使用して、状態を2つの部分に巧みに分割します:

  • 上位16ビット:読み取りステータスを示し、読み取りロック取得の数(再入可能の数を含む)を表します。共有モードにより、複数のスレッドがロックと再入可能を取得できます。
  • 16番目のビット:書き込みステータスを表し、リエントラント書き込みロックの数を表します。排他モードでは、1つのスレッドのみが書き込みロックを取得できますが、リエントラントの数を表すことができます。

2つの違いに注意してください。

/*
         * Read vs write count extraction constants and functions.
         * Lock state is logically divided into two unsigned shorts:
         * The lower one representing the exclusive (writer) lock hold count,
         * and the upper the shared (reader) hold count.
         */

        static final int SHARED_SHIFT   = 16;
		// 共享锁状态单位值 65536
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
		// 共享锁线程最大个数 65535
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
		// 排他锁掩码 65535 二进制表示 15个1
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        /** 返回读锁的获取次数【包括重入次数】 无符号补0右移16位,其实就是获取高16位 */
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** 返回写锁可重入次数 将高16位抹去,其实就是获取低16位 */
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

  • sharedCount:符号なし補数0で、16ビット右にシフトします。実際には、上位16ビットを取得します。
  • ExclusiveCount:上位16ビットを消去すると、実際には下位16ビットが取得されます。
// 记录每个线程持有的读锁数量
		static final class HoldCounter {
            // 持有的读锁数
            int count = 0;
            // Use id, not reference, to avoid garbage retention
            // 线程id
            final long tid = getThreadId(Thread.currentThread());
        }

        /**
         * ThreadLocal subclass. Easiest to explicitly define for sake
         * of deserialization mechanics.
         * ThreadLocal的子类
         */
        static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            // 每个线程都需要记录获取读锁的次数,判断是否重入
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

		// ThreadLocalHoldCounter继承ThreadLocal
		// 存放除去第一个获取读锁线程外的其他线程获取读锁的可重入次数
        private transient ThreadLocalHoldCounter readHolds;
		// 记录最后一个获取读锁的线程获取读锁的可重入次数
        private transient HoldCounter cachedHoldCounter;
		// 记录第一个获取到读锁的线程
        private transient Thread firstReader = null;
		// 记录第一个获取到读锁的线程获取读锁的可重入次数
        private transient int firstReaderHoldCount;


        Sync() {
            // 初始化readHolds
            readHolds = new ThreadLocalHoldCounter();
            // 保证readHolds的内存可见性
            setState(getState()); // ensures visibility of readHolds
        }

書き込みロックの取得

ReentrantReadWriteLockの書き込みロックは、WriteLockによって実装されます。

void lock()

書き込みロックは排他ロックであり、一度に1つのスレッドのみがロックを取得できます。

  • 現在、読み取りロックと書き込みロックを取得しているスレッドがない場合、現在のスレッドは書き込みロックを取得して戻ることができます。
  • 現在のスレッドが読み取りロックと書き込みロックを取得している場合、現在書き込みロックを要求しているスレッドはブロックされ、一時停止されます。

書き込みロックは再入可能ロックです。現在のスレッドがすでにロックを取得している場合、再度取得すると、再入可能回数+1が返されます。

// ReentrantReadWriteLock.WriteLock#lock
	public static class WriteLock implements Lock, java.io.Serializable {
        private final Sync sync;
        public void lock() {
            sync.acquire(1);
        }
    }

	// AQS # acquire
    public final void acquire(int arg) {
        // 调用子类实现的tryAcquire,如果位false,则加入阻塞队列,阻塞
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

	// ReentrantReadWriteLock.Sync#tryAcquire
    abstract static class Sync extends AbstractQueuedSynchronizer {
        protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            // c != 0表示读锁或者写锁已经被某个线程获取了
            if (c != 0) {
                // c != 0 && w == 0表示有线程获取了读锁,share count此时不为0。
                // c != 0 && w != 0并且当前线程不是写锁拥有者,返回false
                // 意思是只要有读锁或写锁被占用,这次获取写锁就会失败
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                
                //走到这里说明当前线程就是已经获取写锁的,判断可重入的次数是否超过了最大值
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 设置可重入的次数,不需要CAS,因为走到这里必然是写锁重入
                setState(c + acquires);
                return true;
            }
            // 走到这,表示 c==0,此时为第一个线程尝试获取写锁。
            // 如果写锁不需要block,进行cas操作,成功则表示获取成功
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            // 经过前面的步骤之后,到这一步,才设置锁的持有者为当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }

ブール型のwriterShouldBlock()

writerShouldBlockメソッドが実装されていますが、公平性と不公平性には違いがあります。

static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            // 返回是否存在前驱节点,会先看看前面有没有在排队的
            return hasQueuedPredecessors();
        }
    }

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        // 总是返回false,直接去cas
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
    }

明らかに、不公平なロックの場合、このメソッドは常にfalseを返します。つまり、compareAndSetState(c、c + Acquisitions)ステップに直接移動し、CASを介して書き込みロックを取得しようとし、取得後の状態を設定します。現在のスレッドがロックホルダーとして設定され、失敗するとfalseが返されます。

つまり、アンフェアモードでは、casが直接書き込みロックを取得しようとし、取得できない場合はキューに入れます。フェアモードの場合、現在のスレッドのブロックキューにプリカーサーノードがあると、次のようになります。書き込みロックをめぐって競合するCASのプロセスをアップします。

void lockInterruptibly()

ReentrantLockのlockInterruptibly()メソッドと同様に、他のスレッドがスレッドのinterrupt()メソッドを呼び出して現在のスレッドに割り込むと、現在のスレッドはInterruptedExceptionをスローします。

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

ブールtryLock()

書き込みロックを取得してみてください。現在、書き込みロックまたは読み取りロックを保持しているスレッドが他にない場合、現在のスレッドは書き込みロックを正常に取得してtrueを返します。

現在、書き込みロックまたは読み取りロックを保持している他のスレッドがある場合、このメソッドは直接falseを返し、現在のスレッドはブロックされません。

現在のスレッドがすでに書き込みロックを保持している場合は、AQSの状態値を増やして、直接trueを返します。

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

// AQS
final boolean tryWriteLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c != 0) {
        int w = exclusiveCount(c);
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    if (!compareAndSetState(c, c + 1))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

実際、ロック方式のロジックはそれほど変わりませんが、ロック方式の不公平なロックロジックが採用されています。

boolean tryLock(long timeout、TimeUnit unit)

ReentrantLockのtryLock(long timeout、TimeUnit unit)メソッドに似ています。

書き込みロックの取得を試みます。取得に失敗した場合、現在のスレッドは指定された時間中断されます。時間が経過すると、現在のスレッドがアクティブになります。それでもロックが取得されない場合は、falseが返されます。

さらに、このメソッドは割り込みに応答します。他のスレッドが現在のスレッドのinterrupt()メソッドを呼び出す場合は、割り込みに応答して例外をスローします。

書き込みロック解除

ボイドunlock()

ロックを解除してみてください。現在のスレッドがロックを保持している場合、このメソッドを呼び出すと、スレッドが保持しているAQS状態が1ずつデクリメントされます。1をデクリメントした後に現在の状態値が0の場合、現在のスレッドはロックを解放します。

現在のスレッドがロックを保持しておらず、このメソッドを呼び出すと、IllegalMonitorStateExceptionがスローされます。

public void lock() {
    sync.acquireShared(1);
}
	// AQS
    public final boolean release(int arg) {
        // 尝试释放锁
        if (tryRelease(arg)) {
            Node h = head;
            // 如果释放成功,叫醒后继节点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
	// ReentrantReadWriteLock.Sync#tryAcquire
    abstract static class Sync extends AbstractQueuedSynchronizer {
        protected final boolean tryRelease(int releases) {
            // 当前线程没有持有该锁而调用了该方法
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            // 判断一下是不是需要释放锁了
            boolean free = exclusiveCount(nextc) == 0;
            // 清空一下
            if (free)
                setExclusiveOwnerThread(null);
            // state没有到0,仅仅是设置state而已
            setState(nextc);
            // 如果写锁全部释放,返回true,上面的方法就会唤醒之后的节点
            return free;
        }
    }


読み取りロックの取得

ReentrantReadWriteLockの読み取りロックはReadLockによって実装されます。ps:読み取りロックの取得と解放は書き込みロックよりも複雑です。

void lock()

// ReentrantReadWriteLock.ReadLock#lock
	public static class ReadLock implements Lock, java.io.Serializable {
        private final Sync sync;
        public void lock() {
            sync.acquireShared(1);
        }
    }

	// AQS # acquireShared
    public final void acquireShared(int arg) {
        // 调用子类实现的tryAcquireShared,如果为false,则加入阻塞队列,阻塞
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

	// ReentrantReadWriteLock.Sync#tryAcquire
    abstract static class Sync extends AbstractQueuedSynchronizer {
        protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, 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.
             */
            Thread current = Thread.currentThread();
            // 获取当前状态值
            int c = getState();
            
            if (exclusiveCount(c) != 0 && // 说明有线程持有写锁
                getExclusiveOwnerThread() != current) // 并且不是当前线程持有写锁
                return -1; // 失败
            
            //----- 这里提一嘴,上面如果持有写锁的是自己,就还是可以获取读锁的 -----//
            
            // 获取读锁计数
            int r = sharedCount(c);
            
            // 读锁获取是否需要阻塞,若不成功将会进入fullTryAcquireShared进行重试
            if (!readerShouldBlock() &&
                r < MAX_COUNT && // 判断读锁获取次数是否溢出
                // 尝试将高16位+1,低16位不变,如果获取成功则表示获取到了读锁
                compareAndSetState(c, c + SHARED_UNIT)) { 
                
                // ----- 能走到这里表示当前线程获取读锁成功 ----- //
                
                // r==0表示第一个线程获取读锁 ,也有可能之前有线程但被释放了,当前自然就是第一个啦
                if (r == 0) {
                    firstReader = current; // 记录一下firstReader【每次将读锁获取次数从0变成1】
                    firstReaderHoldCount = 1; // 记录一下持有的读锁数量 1
                    
                // 来到这里 c != 0 且 firstReader == current ,表示firstReader可重入了
                } else if (firstReader == current) {
                    firstReaderHoldCount++; // 直接计数加1
                } else {
              		// cachedHoldCounter 使用来缓存最后一个获取读锁的线程的,之后用rh表示
                    HoldCounter rh = cachedHoldCounter;
                    
                    // 如果rh还没缓存 或者 存的不是当前线程
                    if (rh == null || rh.tid != getThreadId(current))
                        // 那就更新一下cachedHoldCounter 为当前线程的HoldCounter
                        cachedHoldCounter = rh = readHolds.get();
                    // 走到这里,说明缓存的是当前线程,但是count是0
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    // count 加1
                    rh.count++;
                }
                return 1; // 大于0表示获取到了共享锁
            }
            // 类似tryAcquireShared,自旋获取,这里失败的话,就得进阻塞队列去了嗷
            return fullTryAcquireShared(current);
        }
    }

ブール型readerShouldBlock()

readerShouldBlockメソッドが実装されています。公平性と不公平性には違いがあります。

static final class FairSync extends Sync {

        final boolean readerShouldBlock() {
            // 看看阻塞队列中是否已经有其他元素在排队
            return hasQueuedPredecessors();
        }
    }

    static final class NonfairSync extends Sync {

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

具体的には、不公平なロックの実装、明らかにFirstQueuedIsExclusiveメソッドを見てください。

final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null && // 队列是否为空
            (s = h.next)  != null && // 是否存在第一个元素
            !s.isShared()         && // 第一个元素是否正在尝试获取写锁
            s.thread != null;		 // 该元素的线程是否为null
    }

リンクして説明します。

  1. !readerShouldBlock():キューに要素がある場合は、最初の要素が書き込みロックを取得しようとしているかどうかを判断します。取得している場合は、この要素を最初に配置し、優先度を高くします。
  2. r <MAX_COUNT:現在読み取りロックを取得しているスレッドが最大値に達しているかどうかを判別します。
  3. compareAndSetState(c、c + SHARED_UNIT):CAS操作実行して、AQS状態値の上位16ビット値を1増やします。

実際、キューの最初のノードが書き込みロックを取得しようとしているかどうかを確認します。取得しようとしている場合は、最初に取得させます。読み取りロックを取得している場合は、申し訳ありませんが、CASに素直にアクセスして、誰がそれを取得できるかを確認してください。

読み取りロックが取得されていない場合はどうなりますか?fullTryAcquireSharedロジックを入力して、以下を確認します。

int fullTryAcquireShared(Thread)

/**
         * Full version of acquire for reads, that handles CAS misses
         * and reentrant reads not dealt with in tryAcquireShared.
         */
        final int fullTryAcquireShared(Thread current) {
            /*
             * 这段代码和tryAcquireShared部分冗余,但总体更加简单
             */
            HoldCounter rh = null;
            // 自旋
            for (;;) {
                int c = getState();
                // 已经有线程获取了写锁
                if (exclusiveCount(c) != 0) {
                    // 且获取写锁的线程不是当前线程,那就直接进队,如果是当前线程,走到cas去,锁降级的过程
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                
                } else if (readerShouldBlock()) {
                    // 走到这一步,表示写锁没被占有,且阻塞队列中有其他线程在等待
                    // firstReader线程重入读锁,直接快进到下面的cas
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            // cachedHoldCounter 没有缓存或缓存的不是当前线程
                            if (rh == null || rh.tid != getThreadId(current)) {
                                // 如果当前线程从来没有初始化ThreadLocal中的值,get方法会进行初始化
                                rh = readHolds.get();
                                // 表示上一行代码是初始化的,执行remove
                                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操作成功,表示获取读锁了,接下来设置firstReader或firstReaderHoldCount
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        // 将cachedHoldCounter设置为当前线程
                        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;
                }
            }
        }

ロックの読み取りと書き込みの次のいくつかの方法は実際にはそれほど違いはなく、ソースコードは投稿されず、興味のある友人は自分で見ることができます。

void lockInterruptibly()

lock()メソッドと同様に、このメソッドは応答を中断できる点が異なります。他のスレッドがスレッドのinterrupt()メソッドを呼び出して現在のスレッドを中断すると、現在のスレッドはInterruptedExceptionをスローします。

ブールtryLock()

ロックの読み取りを試みます。現在、書き込みロックを保持しているスレッドが他にない場合、現在のスレッドは読み取りロックを正常に取得し、trueを返します。

現在書き込みロックを保持している他のスレッドがある場合、ブロックせずにfalseを返します。

現在のスレッドがすでに読み取りロックを保持している場合は、AQSを使用して状態の上位16ビットを1増やし、trueを返します。

boolean tryLock(long timeout、TimeUnit unit)

tryLockと同様に、タイムアウト期間が設定され、タイムアウト期間が終了する点が異なります。読み取りロックが読み取られない場合、falseが直接返されます。

応答は中断される可能性があります。他のスレッドがスレッドのinterrupt()メソッドを呼び出して現在のスレッドを中断すると、現在のスレッドはInterruptedExceptionをスローします。

ロック解除の読み取り

ボイドunlock()

public void unlock() {
    sync.releaseShared(1);
}
// AQS
public final boolean releaseShared(int arg) {
    // 如果tryReleaseShared返回true,释放一个由于获取写锁而被阻塞的线程
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
abstract static class Sync extends AbstractQueuedSynchronizer {
    protected final boolean tryReleaseShared(int unused) {
        Thread current = Thread.currentThread();
        if (firstReader == current) {
            // 如果firstReaderHoldCount为1,这次解锁之后,就会变成0了,将firstReader设置为null
            if (firstReaderHoldCount == 1)
                firstReader = null;
            else
                // 否则减1就可以
                firstReaderHoldCount--;
        } else {
            // 判断cacheHoldCounter是否缓存的是当前线程,如果不是的话,需要从ThreadLocal中取。
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                rh = readHolds.get();
            int count = rh.count;
            if (count <= 1) {
                // 不再持有锁了,调用remove,防止内存泄露
                readHolds.remove();
                if (count <= 0)
                    throw unmatchedUnlockException();
            }
            --rh.count;
        }
        // 无限循环,保证CAS操作成功
        for (;;) {
            // 获取状态值
            int c = getState();
            int nextc = c - SHARED_UNIT;
            // CAS  操作更新状态值。CAS操作如果不成功,会一直循环
            if (compareAndSetState(c, nextc))
                // 如果更新成功,查看当前状态值是否为0,如果为0说明已经没有读线程占用读锁
                // 如果不为0,则说明还有其他线程持有读锁,返回false
                return nextc == 0;
        }
    }
}




ロック劣化の理解

そのロックの格下げ手段書き込みロックが読み取りロックにダウングレードすることができますが、一連の書き込みロックを、書き込みロックを取得する読み取りロックを取得してから解放するには従わなければなりません現在のスレッドが最初に書き込みロックを取得し、次に書き込みロックを解放し、次に読み取りロックを取得する場合、このプロセスをロック劣化と呼ぶことはできません。ロック劣化はその順序に従う必要があります。

著者のDougLeaは、書き込みロックがより高度であるとは言っていないことに注意してください。スレッドが読み取りロックを保持している場合、書き込みロックの取得も待機する必要がありますが、ソースコードでは、書き込みロックに特別な注意が払われていることがわかります。不公平モードなどスループットを向上させるために、最初のノードが書き込みロックを取得するスレッドであることが判明した場合、取得は直接成功します。

ロックダウングレードの一部は、ソースコードに反映されています。

int c = getState();
// 已经有线程获取了写锁
if (exclusiveCount(c) != 0) {
    // 且获取写锁的线程不是当前线程,那就直接进队,如果是当前线程,走到cas去,锁降级的过程
    if (getExclusiveOwnerThread() != current)
        return -1;

ロックの低下時に読み取りロックを取得する必要がありますか?

現在のスレッドAが読み取りロックを取得せず、書き込みロックを直接解放する場合、この時点で別のスレッドBが書き込みロックを取得すると、このスレッドBのデータ変更は現在のスレッドAには表示されません。読み取りロックが取得されると、スレッドBは、書き込みロックの取得プロセス中に解放されていない読み取りロックがある場合、それをブロックすると判断します。現在のスレッドAが読み取りロックを解放した後でのみ、スレッドBは書き込みロックを正常に取得します。

総括する

  • ReentrantReadWriteLockの最下層はAQSを使用して実装されます。AQSステータス値の上位16ビットは取得された読み取りロックの数を示し、下位16ビットは書き込みロックを取得した再入可能スレッドの数を示します。CASはで動作します。読み取りと書き込みの分離を実現します。、読み取りを増やして書き込みを減らすシナリオに適しています。
  • ReentrantReadWriteLockの3つの機能:公平性:公平モードと不公平モードの両方をサポートします。再入可能:再入可能をサポートし、最大65535の読み取り/書き込みロックをサポートします。ロックのダウングレード:最初に書き込みロックを取得し、次に読み取りロックを取得してから、書き込みロックを解放すると、書き込みロックを読み取りロックにダウングレードできます。
  • 読み取り/書き込みロック:読み取り/書き込みロックを使用すると、複数のリーダースレッドが同時にアクセスできますが、書き込みスレッドがアクセスすると、すべてのリーダースレッドと他の書き込みスレッドがブロックされます。

元のリンク:https://www.cnblogs.com/summerday152/p/14284646.html

この記事がお役に立てば幸いです。私の公式アカウントをフォローし、キーワード[インタビュー]に返信して、Javaのコアナレッジポイントとインタビューギフトパッケージをまとめてください。共有する技術的な乾物の記事や関連資料がもっとあります。みんなが一緒に学び、進歩できるようにしましょう!

おすすめ

転載: blog.csdn.net/weixin_48182198/article/details/112775206