ReentrantReadWriteLock読み書きロック解析を再読み込み可能


ReentrantReadWriteLock再読み込み可能な読み書きロック
再読み込み可能とは、同じスレッドでロックを繰り返すことができ、同じロックを複数回追加することができ、解放されるたびに1回解放され、スレッドのロック回数が0になるまで、このスレッドはロックを解放します.
リード・ライト・ロックとは、リード・ロックが共有され、複数のスレッドが同時にリード・ロックを持つことができますが、ライト・ロックは1つのスレッドしか所有できません.また、ライト・ロックを取得すると、他のスレッドはリード・ロックを解放し、そのスレッドがライト・ロックを取得した後、他のスレッドはリード・ロックを取得できません.
 
次の2つの例を見てみましょうJAvaが持参した2つの例
 * 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();
 *   }
 * }

cacheの内容を更新する場合は、書き込みロックを付けなければなりません.読み取りだけなら、読み取りロックをかければいいです.
 * 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(); }
 *    }
 * }}

この例では、mapに対してget、allKeys読み取り操作にリードロック、put、clear操作にライトロックを加えます.
 
まずReentrantReadWriteLockというクラスの2つの構造関数を見てみましょう.
    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);
    }

fairというパラメータは、公平な読み書きロックを作成するか、それとも非公平な読み書きロックを作成するかを示します.つまりプリエンプト式か非プリエンプト式かです.
 
公平と非公平:公平は、取得したロックの順序が、ロックを取得したスレッドをスレッドロックの順序で割り当てるときに最初にロックされたスレッドを表し、FIFOの順序でロックを割り当てる.非公平は、ロックを取得する順序が不要であることを示し、その後、ロックされたスレッドがロックを取得する可能性があります.この場合、一部のスレッドはロックを取得していない可能性があります.
 
フェアロックはなぜ性能に影響するのか、codeから見てフェアロックはただ1つの検査がチームの頭で性能に影響するかどうか、そうでなければ、どこで影響しているのか.侵入したスレッドであれば、列の最後に並んで寝て前のノードが目覚めるのを待つので、非公平ロックよりも多くのpakingとunparkingの操作が追加されるに違いない.
 
一般的な適用シーンは、複数のリードスレッド、1つのライトスレッド、およびライトスレッドが操作中にリードスレッドをブロックする必要がある場合、公平なロックを使用する必要があります.そうしないと、ライトスレッドがロックを取得できず、スレッドが餓死する可能性があります.
 
私はここで1つのプロジェクトで読み書きロックを使いました.
 
KVエンジンのjavaクライアント:javaクライアントはKVエンジンサーバのクラスタ構成情報をキャッシュする必要がある(master server,data node service)複数のmapのデータ構造;更新時にバックグラウンドの1つの書き込みスレッドによってタイミング的に更新される;読み出しは同時に数十個のスレッドが同時に読むことができる.読み出し構成タイミングが1つのkeyに対応するdata nodeserverに位置してデータを取得する必要があるため;これらのキャッシュされたデータの読み書き同期による問題をよりよく避けるために、読み書きロックを使用するを使用して、同期の問題を解決します.読むときは読解ロック、書くときは書くロックをかけ、書く過程で読むのをブロックする必要があります.書く過程が非常に速いので、読むのをブロックすることができます.重要なのは、書き込みのプロセスがリードスレッドに一部のデータが古いもの、一部が新しいものに読み取られず、取得結果が失敗したり、エラーが発生したりすることです.このcaseは明らかにリードスレッドが多く、ライトスレッドは1つしかなく、テスト中にライトスレッドがロックを取得できないことが分かった.非公平ロックを使用しているので、後にAPIをクエリーすることで、公平ロックを使用すればよい.
 
 
次に、読み書きロックの実装について説明します.
 
読み込みロックを取得する手順は、次のとおりです.
 
        protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail
             * 2. If count saturated, throw error
             * 3. 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.
             * 4. If step 3 fails either because thread
             *    apparently not eligible or CAS fails,
             *    chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            if (sharedCount(c) == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            if (!readerShouldBlock(current) &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != current.getId())
                    cachedHoldCounter = rh = readHolds.get();
                rh.count++;
                return 1;
            }
            return fullTryAcquireShared(current);
        }        /**
         * Full version of acquire for reads, that handles CAS misses
         * and reentrant reads not dealt with in tryAcquireShared.
         */
        final int fullTryAcquireShared(Thread current) {
            /*
             * This code is in part redundant with that in
             * tryAcquireShared but is simpler overall by not
             * complicating tryAcquireShared with interactions between
             * retries and lazily reading hold counts.
             */
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != current.getId())
                rh = readHolds.get();
            for (;;) {
                int c = getState();
                int w = exclusiveCount(c);
                if ((w != 0 && getExclusiveOwnerThread() != current) ||
                    ((rh.count | w) == 0 && readerShouldBlock(current)))
                    return -1;
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    cachedHoldCounter = rh; // cache for release
                    rh.count++;
                    return 1;
                }
            }
        }

書き込みロックが取得された場合、読み取りロックの取得に失敗します.現在のスレッドの再ロック回数がMAX_に達した場合COUNT、リードロックの取得に失敗しました.readerShouldBlock公平ロックであれば、当然スレッドがキューの前に並んでいるかどうかを判断し、そうでなければ待機し、そうでなければリードロックを取得する.再入性とは、class ThreadLocalHoldCounter extends ThreadLocalthreadlocalによって再入取得ロックを保存する回数である.次にfullTryAcquireSharedメソッドを呼び出して、現在のスレッドがロックを取得した回数を操作します.
 
 
書き込みロックを取得する手順は、次のとおりです.
 
フェアロックロックロックロックの取得プロセス:
         * 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 (isFirst(current) &&
                    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;
        }

現在のスレッドが待ち行列のヘッダスレッドであるか否かを判断し、もしそうであれば、現在の反発ロックスレッドを現在のスレッドに設定し、書き込みロックを取得することに成功する.そうでなければ、現在の書き込みロックのスレッドが現在のスレッドであるか否かを判断し、そうであれば書き込みロックの再入数に1を加算する.書き込みロックの取得に失敗しました.
 
非公平な書き込みロック取得プロセス:
 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;
        }

フェアライトロック取得プロセスとは異なり、現在のスレッドが待機キュースレッドの最初のスレッドであるかどうかを判断していない.