ReentrantReadWriteLock Lese- / Schreibsperrenanalyse

Übersicht über ReadWriteLock

Wie bereits erwähnt, handelt es sich bei ReentrantLock um eine exklusive Sperre. Es kann jeweils nur ein Thread die Sperre erwerben. In der Tat gibt es viele Szenarien, in denen das Lesen mehr und weniger schreibt und der Lesevorgang selbst keine Probleme mit dem Datenwettbewerb aufweist Wenn Sie eine exklusive Sperre verwenden, kann einer der Reader-Threads auf andere Reader-Threads warten, was die Leistung beeinträchtigt.

Als Reaktion auf dieses Szenario, in dem mehr gelesen und weniger geschrieben wurde, entstanden Lese- / Schreibsperren. Die Lese- / Schreibsperre ermöglicht den gleichzeitigen Zugriff mehrerer Reader-Threads. Wenn jedoch der Writer-Thread zugreift, werden alle Reader-Threads und andere Writer-Threads blockiert. Werfen wir einen Blick auf die oberste Schnittstelle der Lese- / Schreibsperren in Java, die sich in folgendem Dokument befindet: java.util.concurrent.locks package:

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

Ich glaube, Sie werden sofort verstehen, dass die Lese- / Schreibsperre tatsächlich ein Paar Sperren, eine Schreibsperre und eine Lesesperre aufrechterhält. Durch die Strategie der Lese- / Schreibtrennung können mehrere Threads gleichzeitig Lesesperren erwerben , die Parallelität erheblich verbessern.

Lese- / Schreibsperre

Das JavaDoc-Dokument ist sehr detailliert und gibt uns ein Beispiel für die Verwendung von ReentrantReadWriteLock. Schauen wir uns das direkt an:

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

Fassen Sie im folgenden Analyseteil ein wenig zusammen:

ReentrantReadWriteLock Lese- / Schreibsperren sind in Lesesperren und Schreibsperren unterteilt. Lesesperren sind gemeinsam genutzte Sperren und Schreibsperren sind exklusive Sperren. Der Thread, der die Schreibsperre hält, kann weiterhin die Lesesperre erhalten, die als Sperrenverschlechterung bezeichnet wird.

Übersicht über die ReentrantReadWriteLock-Architektur

ReentrantReadWriteLock ist die Implementierung von ReadWriteLock. Wenn wir den Namen: reentrant read-write lock sehen, können wir wahrscheinlich seine Bedeutung erraten. Neben der Implementierung der Methoden readLock () und writeLock () werden einige wichtige Methoden bereitgestellt, die wir später analysieren werden.

ReentrantReadWriteLock Lese- / Schreibsperrenanalyse

 

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等方法
    }

Fassen wir zusammen:

  • ReentrantReadWriteLock verwaltet zwei interne Klassen, ReadLock und WriteLock, die beide Sync mit der Implementierung bestimmter Funktionen beauftragen [Sync ist die Implementierung von AQS, von der ich bereits sagte, dass sie sehr klar ist].
  • Wie ReentrantLock bietet es auch zwei Implementierungen von Fairness und Ungerechtigkeit: FairSync und NonfairSync. Sie sind Implementierungsklassen von Sync. Die beiden Unterschiede finden Sie unter: Unterschiede zwischen fairen und unfairen Strategien
  • ReadLock- und WriteLock-Instanzen können mit zwei Methoden abgerufen werden: readLock () und writeLock ().
  • ReadLock verwendet den gemeinsam genutzten Modus und WriteLock den exklusiven Modus. Den Unterschied zwischen beiden finden Sie unter: Java Concurrent Package-Quellcode-Lernserie: Der Unterschied zwischen gemeinsam genutzten und exklusiven AQS-Erwerb und Freigabe von Ressourcen.

Synchronisieren Sie wichtige Felder und die interne Klassendarstellung

Wie bereits beim Erlernen von AQS erwähnt, ist der Schlüssel von AQS der Feldstatus des Synchronisationsstatus. Am Beispiel von ReentrantLock ist der Status 0, um anzuzeigen, dass die Sperre frei ist, 1, um anzuzeigen, dass die Sperre erworben wurde, und größer als 1, um anzuzeigen, dass die Sperre von demselben Thread verwendet wird. Wiedereintritt.

Es ist jedoch bekannt, dass die Lese- / Schreibsperre zwei Zustände beibehalten muss. Wie kann sie mit nur einem ganzzahligen Variablenzustand dargestellt werden? Die Lese- / Schreibsperre verwendet die Idee des bitweisen Schneidens, um den Zustand geschickt in zwei Teile zu unterteilen:

  • Hohe 16 Bit: Zeigt den Lesestatus an, der die Anzahl der Leseverriegelungserfassungen [einschließlich der Anzahl der Wiedereintritte] darstellt. Aufgrund des Freigabemodus können mehrere Threads die Sperre und den Wiedereintritt erfassen.
  • Das 16. Bit: Stellt den Schreibstatus dar, der die Anzahl der Wiedereintrittsschreibsperren darstellt. Im exklusiven Modus kann nur ein Thread die Schreibsperre erhalten, aber die Anzahl der Wiedereintrittssperren.

Beachten Sie den Unterschied zwischen den beiden.

/*
         * 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: vorzeichenloses Komplement 0 und Verschiebung um 16 Bit nach rechts, um die hohen 16 Bit zu erhalten.
  • ExclusiveCount: Wenn Sie die hohen 16 Bits löschen, erhalten Sie tatsächlich die niedrigen 16 Bits.
// 记录每个线程持有的读锁数量
		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
        }

Erwerb der Schreibsperre

Die Schreibsperre in ReentrantReadWriteLock wird von WriteLock implementiert.

void lock ()

Die Schreibsperre ist eine exklusive Sperre, und jeweils nur ein Thread kann die Sperre erwerben.

  • Wenn derzeit kein Thread die Lese- und Schreibsperre erhält, kann der aktuelle Thread die Schreibsperre erwerben und zurückkehren.
  • Wenn der aktuelle Thread die Lese- und Schreibsperre erhalten hat, wird der Thread, der gerade die Schreibsperre anfordert, blockiert und angehalten.

Die Schreibsperre ist eine Wiedereintrittssperre. Wenn der aktuelle Thread die Sperre bereits erhalten hat, gibt die erneute Erfassung einfach die Anzahl der Wiedereintrittszeiten +1 zurück.

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

boolean writerShouldBlock ()

Wenn die writerShouldBlock-Methode implementiert ist, gibt es einen Unterschied zwischen Fairness und Ungerechtigkeit:

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
        }
    }

Bei unfairen Sperren gibt diese Methode natürlich immer false zurück. Dies bedeutet, dass sie direkt zum Schritt compareAndSetState (c, c + Acquires) übergeht, versucht, die Schreibsperre über CAS zu erhalten, und den Status nach der Erfassung festlegt ist erfolgreich. Der aktuelle Thread wird als Sperrhalter festgelegt und false wird bei einem Fehler zurückgegeben.

Dies bedeutet: Im unfairen Modus wird cas direkt versucht, die Schreibsperre zu aktivieren, und dann in die Warteschlange gestellt, wenn sie nicht erfasst werden kann. Im fairen Modus gibt der aktuelle Thread einen Vorläuferknoten in der blockierenden Warteschlange aus den Prozess des CAS, der um die Schreibsperre konkurriert.

void lockInterruptibly ()

Ähnlich wie bei der lockInterruptibly () -Methode von ReentrantLock löst der aktuelle Thread eine InterruptedException aus, wenn andere Threads die Interrupt () -Methode des Threads aufrufen, um den aktuellen Thread zu unterbrechen.

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

boolean tryLock ()

Versuchen Sie, eine Schreibsperre zu erhalten. Wenn derzeit kein anderer Thread eine Schreibsperre oder eine Lesesperre besitzt, erhält der aktuelle Thread die Schreibsperre erfolgreich und gibt true zurück.

Wenn derzeit andere Threads eine Schreibsperre oder eine Lesesperre haben, gibt diese Methode direkt false zurück und der aktuelle Thread wird nicht blockiert.

Wenn der aktuelle Thread bereits die Schreibsperre enthält, erhöhen Sie einfach den Statuswert von AQS und geben true direkt zurück.

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

Tatsächlich ist die Logik der Sperrmethode nicht sehr unterschiedlich, aber die unfaire Sperrlogik der Sperrmethode wird übernommen.

boolean tryLock (lange Zeitüberschreitung, TimeUnit-Einheit)

Ähnlich der tryLock-Methode (Long Timeout, TimeUnit-Einheit) von ReentrantLock.

Versuchen Sie, die Schreibsperre zu erlangen. Wenn die Erfassung fehlschlägt, wird der aktuelle Thread für eine bestimmte Zeit angehalten. Nach Ablauf der Zeit wird der aktuelle Thread aktiviert. Wenn die Sperre immer noch nicht erlangt wird, wird false zurückgegeben.

Darüber hinaus reagiert diese Methode auf Interrupts. Wenn andere Threads die Interrupt () -Methode des aktuellen Threads aufrufen, reagieren Sie auf den Interrupt und lösen Sie eine Ausnahme aus.

Schreibsperre schreiben

nichtig entsperren ()

Versuchen Sie, die Sperre aufzuheben. Wenn der aktuelle Thread die Sperre hält, wird durch Aufrufen dieser Methode der vom Thread gehaltene AQS-Status um 1 verringert. Wenn der aktuelle Statuswert nach dem Verringern von 1 0 ist, gibt der aktuelle Thread die Sperre auf.

Wenn der aktuelle Thread die Sperre nicht hält und diese Methode aufruft, wird eine IllegalMonitorStateException ausgelöst.

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


Erwerb der Lesesperre

Die Lesesperre in ReentrantReadWriteLock wird von ReadLock implementiert. Ps: Das Erfassen und Freigeben von Lesesperren ist komplizierter als das Schreiben von Sperren.

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

boolean readerShouldBlock ()

Die readerShouldBlock-Methode ist implementiert, es gibt einen Unterschied zwischen Fairness und Ungerechtigkeit:

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

Betrachten Sie insbesondere die Implementierung unfairer Sperren, anscheinendFirstQueuedIsExclusive-Methode:

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

Verknüpfen und erklären:

  1. ! readerShouldBlock (): Wenn sich ein Element in der Warteschlange befindet, bestimmen Sie, ob das erste Element versucht, eine Schreibsperre zu erhalten. Wenn dies der Fall ist, lassen Sie dieses Element an erster Stelle stehen und seine Priorität ist hoch.
  2. r <MAX_COUNT: Bestimmen Sie, ob der Thread, der gerade die Lesesperre erhält, den Maximalwert erreicht.
  3. compareAndSetState (c, c + SHARED_UNIT): Führen Sie eine CAS-Operation aus , um den hohen 16-Bit-Wert des AQS- Statuswerts um 1 zu erhöhen .

In der Tat: Sehen Sie, ob der erste Knoten in der Warteschlange versucht, die Schreibsperre zu erhalten . Wenn ja, lassen Sie ihn zuerst kommen. Wenn Sie eine Lesesperre erwerben, tut es mir leid, gehen Sie gehorsam zu CAS und sehen Sie, wer sie bekommen kann.

Was passiert, wenn die Lesesperre nicht erreicht wird? Geben Sie die fullTryAcquireShared-Logik ein, um Folgendes anzuzeigen:

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

Die nächsten Methoden zum Lesen und Schreiben von Sperren unterscheiden sich nicht wesentlich, der Quellcode wird nicht veröffentlicht und interessierte Freunde können sich selbst davon überzeugen.

void lockInterruptibly ()

Ähnlich wie bei der lock () -Methode besteht der Unterschied darin, dass diese Methode die Antwort unterbrechen kann. Wenn andere Threads die Interrupt () -Methode des Threads aufrufen, um den aktuellen Thread zu unterbrechen, löst der aktuelle Thread eine InterruptedException aus.

boolean tryLock ()

Versuchen Sie, die Sperre zu lesen. Wenn derzeit kein anderer Thread die Schreibsperre hält, erhält der aktuelle Thread die Lesesperre erfolgreich und gibt true zurück.

Wenn derzeit andere Threads die Schreibsperre halten, wird false zurückgegeben, ohne zu blockieren.

Wenn der aktuelle Thread bereits die Lesesperre enthält, erhöhen Sie mit AQS die hohen 16 Statusbits um 1 und geben true zurück.

boolean tryLock (lange Zeitüberschreitung, TimeUnit-Einheit)

Ähnlich wie bei tryLock besteht der Unterschied darin, dass die Zeitüberschreitungsperiode festgelegt und die Zeitüberschreitungsperiode abgelaufen ist. Wenn die Lesesperre nicht gelesen wird, wird false direkt zurückgegeben.

Die Antwort kann unterbrochen werden. Wenn andere Threads die Interrupt () -Methode des Threads aufrufen, um den aktuellen Thread zu unterbrechen, löst der aktuelle Thread eine InterruptedException aus.

Sperrenfreigabe lesen

nichtig entsperren ()

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




Verständnis der Schlossverschlechterung

Lock Herabstufung bedeutet , dass eine Schreibsperre kann auf eine Lesesperre herabgestuft werden , aber die Reihenfolge der die Schreibsperre erwerben, die Lesesperre zu erwerben und dann die Freigabe der Schreibsperre müssen eingehalten werden . Beachten Sie, dass dieser Vorgang nicht als Sperrenverschlechterung bezeichnet werden kann, wenn der aktuelle Thread zuerst die Schreibsperre erwirbt, dann die Schreibsperre aufhebt und dann die Lesesperre erwirbt. Die Sperrverschlechterung muss in dieser Reihenfolge erfolgen.

Beachten Sie, dass der Autor Doug Lea nicht gesagt hat, dass Schreibsperren weiter fortgeschritten sind. Wenn ein Thread eine Lesesperre enthält, muss die Erfassung der Schreibsperre ebenfalls warten. Im Quellcode ist jedoch ersichtlich, dass Schreibsperren besondere Sorgfalt erhalten. B. im unfairen Modus Um den Durchsatz zu verbessern, ist die Erfassung direkt erfolgreich, wenn festgestellt wird, dass der erste Knoten der Thread ist, der die Schreibsperre erwirbt.

Der Teil des Sperren-Downgrades spiegelt sich im Quellcode wider:

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

Ist es notwendig, während der Sperrverschlechterung eine Lesesperre zu erwerben?

Wenn der aktuelle Thread A die Lesesperre nicht erhält, sondern die Schreibsperre direkt aufhebt, erwirbt zu diesem Zeitpunkt ein anderer Thread B die Schreibsperre, ist die Datenänderung dieses Threads B für den aktuellen Thread A nicht sichtbar. Wenn eine Lesesperre erworben wird, beurteilt Thread B, dass eine Lesesperre, die während des Erwerbs einer Schreibsperre nicht freigegeben wurde, blockiert wird. Erst nachdem der aktuelle Thread A die Lesesperre aufgehoben hat, wird Thread B. Erwerben Sie erfolgreich die Schreibsperre.

um zusammenzufassen

  • Die unterste Schicht von ReentrantReadWriteLock wird mithilfe von AQS implementiert. Die oberen 16 Bits des AQS-Statuswerts geben die Anzahl der erfassten Lesesperren an, und die unteren 16 Bits geben die Anzahl der wiedereintrittsfähigen Threads an, die die Schreibsperre erhalten haben. Das CAS wird bearbeitet Um eine Lese-Schreib-Trennung zu erreichen, eignet es sich für Szenarien, in denen mehr gelesen und weniger geschrieben wird.
  • Drei Funktionen von ReentrantReadWriteLock: Fairness: Unterstützt sowohl faire als auch unfaire Modi. Wiedereintritt: Unterstützt Wiedereintritt, Lese- / Schreibsperren unterstützen bis zu 65535. Downgrade der Sperre: Erwerben Sie zuerst die Schreibsperre, dann die Lesesperre und lassen Sie dann die Schreibsperre los. Die Schreibsperre kann auf eine Lesesperre herabgestuft werden.
  • Lese- / Schreibsperre: Mit der Lese- / Schreibsperre können mehrere Leser-Threads gleichzeitig zugreifen. Wenn jedoch auf den Schreib-Thread zugegriffen wird, werden alle Leser-Threads und andere Schreib-Threads blockiert.

Ursprünglicher Link: https://www.cnblogs.com/summerday152/p/14284646.html

Wenn Sie der Meinung sind, dass dieser Artikel für Sie hilfreich ist, können Sie meinem offiziellen Konto folgen und auf das Schlüsselwort [Interview] antworten, um eine Zusammenstellung der Java-Kernwissenspunkte und ein Interview-Geschenkpaket zu erhalten! Es gibt mehr technische Trockenwarenartikel und verwandte Materialien zum Teilen, damit alle gemeinsam lernen und Fortschritte machen können!

Ich denke du magst

Origin blog.csdn.net/weixin_48182198/article/details/112775206
Empfohlen
Rangfolge