Análise de bloqueio de leitura e gravação ReentrantReadWriteLock

Visão geral de ReadWriteLock

Como dissemos antes, ReentrantLock é um bloqueio exclusivo. Apenas um thread pode adquirir o bloqueio por vez. Na verdade, haverá muitos cenários onde ler mais e menos escrever , e a operação de leitura em si não terá problemas de competição de dados. Se você usar um bloqueio exclusivo, pode fazer com que um dos threads do leitor espere por outros threads do leitor, reduzindo o desempenho.

Em resposta a este cenário em que ler mais e escrever menos, os bloqueios de leitura e gravação surgiram. O bloqueio de leitura e gravação permite que vários threads do leitor acessem ao mesmo tempo, mas quando o thread do gravador acessa, todos os threads do leitor e outros threads do gravador são bloqueados. Vamos dar uma olhada na interface de nível superior dos bloqueios de leitura e gravação em Java, que está localizada em: pacote java.util.concurrent.locks:

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

Eu acredito que você vai entender imediatamente que o bloqueio de leitura e gravação está, na verdade, mantendo um par de bloqueios, um bloqueio de gravação e um bloqueio de leitura. Através da estratégia de separação de leitura e gravação, vários threads podem adquirir bloqueios de leitura ao mesmo tempo , melhorando muito a simultaneidade.

Caso de bloqueio de leitura e gravação

O documento JavaDoc é muito detalhado e nos dá um exemplo do uso de ReentrantReadWriteLock. Vamos dar uma olhada diretamente:

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

Resuma um pouco, em detalhes na seguinte parte de análise:

Os bloqueios de leitura e gravação ReentrantReadWriteLock são divididos em bloqueios de leitura e de gravação. Os bloqueios de leitura são compartilhados e os bloqueios de gravação são exclusivos. O encadeamento que contém o bloqueio de gravação pode continuar a adquirir o bloqueio de leitura, que é chamado de degradação de bloqueio.

Visão geral da arquitetura ReentrantReadWriteLock

ReentrantReadWriteLock é a implementação de ReadWriteLock. De fato, vendo o nome: bloqueio reentrante de leitura e gravação, provavelmente podemos adivinhar seu significado. Além de implementar os métodos readLock () e writeLock (), ele também fornece alguns métodos importantes, que analisaremos posteriormente.

Análise de bloqueio de leitura e gravação 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等方法
    }

Vamos resumir:

  • ReentrantReadWriteLock mantém duas classes internas, ReadLock e WriteLock, ambas as quais confiam em Sync para implementar funções específicas [Sync é a implementação de AQS, o que eu disse antes é muito claro].
  • Como ReentrantLock, ele também fornece duas implementações de justiça e injustiça: FairSync e NonfairSync. Eles são classes de implementação de Sync. Para as duas diferenças, consulte: Diferenças entre estratégias justas e injustas
  • As instâncias ReadLock e WriteLock podem ser obtidas por meio de dois métodos: readLock () e writeLock ().
  • ReadLock usa modo compartilhado e WriteLock usa modo exclusivo.Para a diferença entre os dois, consulte: Série de aprendizado de código-fonte de pacote simultâneo Java: A diferença entre AQS compartilhada e aquisição exclusiva e liberação de recursos.

Sincronizar campos importantes e representação de classe interna

Como mencionamos ao aprender AQS, a chave do AQS é o estado do campo do estado de sincronização. Por exemplo, tomando ReentrantLock como exemplo, seu estado é 0 para indicar que o bloqueio está livre, 1 para indicar que o bloqueio foi adquirido e maior de 1 para indicar que o bloqueio está sendo usado pelo mesmo segmento.

Porém, sabe-se que o bloqueio de leitura e escrita precisa manter dois estados, como representá-lo com apenas um estado de variável inteira? O bloqueio de leitura e gravação usa a ideia de corte bit a bit para dividir habilmente o estado em duas partes:

  • 16 bits altos: indica o status de leitura, representando o número de aquisições de bloqueio de leitura [incluindo o número de reentrantes]. Devido ao modo de compartilhamento, vários threads podem adquirir o bloqueio e reentrante.
  • O 16º bit: Representa o status de gravação, representando o número de bloqueios de gravação reentrantes.No modo exclusivo, apenas um thread pode obter o bloqueio de gravação, mas pode representar o número de reentrantes.

Observe a diferença entre os dois.

/*
         * 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: complemento 0 sem sinal e deslocamento para a direita em 16 bits, na verdade, é para obter os 16 bits altos.
  • exclusiveCount: Apagar os 16 bits mais altos está, na verdade, obtendo os 16 bits mais baixos.
// 记录每个线程持有的读锁数量
		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
        }

Aquisição de bloqueio de escrita

O bloqueio de gravação em ReentrantReadWriteLock é implementado por WriteLock.

void lock ()

O bloqueio de gravação é um bloqueio exclusivo e apenas um encadeamento pode adquirir o bloqueio por vez.

  • Se nenhum encadeamento adquire atualmente o bloqueio de leitura e o bloqueio de gravação, o encadeamento atual pode adquirir o bloqueio de gravação e retornar.
  • Se a thread atual adquiriu o bloqueio de leitura e gravação, a thread que está solicitando o bloqueio de gravação será bloqueada e suspensa.

O bloqueio de gravação é um bloqueio reentrante. Se o thread atual já adquiriu o bloqueio, adquiri-lo novamente simplesmente retorna o número de reentrantes vezes +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;
        }
    }

boolean writerShouldBlock ()

O método writerShouldBlock é implementado, há uma diferença entre justiça e injustiça:

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

Obviamente, para bloqueios injustos, esse método sempre retornará falso, o que significa que irá e irá diretamente para a etapa compareAndSetState (c, c + adquire), tente adquirir o bloqueio de gravação por meio do CAS e definirá o estado após a aquisição for bem-sucedido. O thread atual será definido como o detentor do bloqueio e false será retornado em caso de falha.

Isso significa: no modo injusto, ele tentará diretamente o cas pegar o bloqueio de gravação e, em seguida, enfileirar se não puder ser capturado; para o modo justo, se a thread atual tiver um nó precursor na fila de bloqueio, ele dará o processo de CAS competindo pelo bloqueio de gravação.

void lockInterruptibly ()

Semelhante ao método lockInterruptibly () de ReentrantLock, quando outras threads chamam o método interrupt () da thread para interromper a thread atual, a thread atual lançará uma 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);
    }

boolean tryLock ()

Tente adquirir um bloqueio de gravação. Se nenhum outro thread atualmente mantém um bloqueio de gravação ou de leitura, o thread atual irá adquirir o bloqueio de gravação e retornar true.

Se houver atualmente outros threads segurando um bloqueio de gravação ou leitura, este método retorna diretamente falso e o thread atual não será bloqueado.

Se a thread atual já contém o bloqueio de gravação, simplesmente aumente o valor do estado de AQS e retorne true diretamente.

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

Na verdade, a lógica do método de bloqueio não é muito diferente, mas a lógica de bloqueio injusta do método de bloqueio é adotada.

boolean tryLock (longo tempo limite, unidade TimeUnit)

Semelhante ao método tryLock (tempo limite longo, unidade TimeUnit) de ReentrantLock.

Tente adquirir o bloqueio de gravação. Se a aquisição falhar, o thread atual será suspenso por um tempo especificado. Após o tempo acabar, o thread atual será ativado. Se o bloqueio ainda não for adquirido, false será retornado.

Além disso, este método responderá a interrupções. Se outros threads chamarem o método interrupt () do thread atual, responda à interrupção e lance uma exceção.

Liberação de bloqueio de gravação

void unlock ()

Tente liberar o bloqueio. Se o thread atual mantém o bloqueio, chamar este método irá diminuir o estado AQS mantido pelo thread em 1; se o valor do estado atual for 0 após diminuir 1, o thread atual irá liberar o bloqueio.

Se o segmento atual não retém o bloqueio e chama este método, uma IllegalMonitorStateException é lançada.

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


Aquisição de bloqueio de leitura

O bloqueio de leitura em ReentrantReadWriteLock é implementado por ReadLock ps: A aquisição e liberação de bloqueios de leitura é mais complicada do que os bloqueios de gravação.

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

O método readerShouldBlock é implementado, há uma diferença entre justiça e injustiça:

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

Observe especificamente a implementação de bloqueios injustos, método aparentementeFirstQueuedIsExclusive:

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

Ligue-se e explique:

  1. ! readerShouldBlock (): Se houver um elemento na fila, determine se o primeiro elemento está tentando adquirir um bloqueio de gravação. Se estiver, deixe este elemento vir primeiro e sua prioridade é alta.
  2. r <MAX_COUNT: determina se a thread atualmente adquirindo o bloqueio de leitura atinge o valor máximo.
  3. compareAndSetState (c, c + SHARED_UNIT): Execute a operação CAS para aumentar o valor alto de 16 bits do valor do estado AQS em 1 .

Na verdade: veja se o primeiro nó na fila está tentando adquirir o bloqueio de gravação , em caso afirmativo, deixe-o vir primeiro. Se você está adquirindo um bloqueio de leitura, desculpe, vá ao CAS obedientemente e veja quem pode obtê-lo.

O que acontece se o bloqueio de leitura não for adquirido? Digite a lógica completa TryAcquireShared para ver:

int fullExperimenteAcquireShared (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;
                }
            }
        }

Os próximos métodos de leitura e gravação de bloqueios não são muito diferentes, o código-fonte não será postado e amigos interessados ​​podem ver por si mesmos.

void lockInterruptibly ()

Semelhante ao método lock (), a diferença é que este método pode interromper a resposta.Quando outros threads chamam o método interrupt () do thread para interromper o thread atual, o thread atual lança uma InterruptedException.

boolean tryLock ()

Tente ler o bloqueio. Se nenhum outro segmento atualmente mantém o bloqueio de gravação, o segmento atual irá adquirir o bloqueio de leitura com sucesso e retornar verdadeiro.

Se houver outros threads atualmente segurando o bloqueio de gravação, ele retornará falso sem bloqueio.

Se o encadeamento atual já contém o bloqueio de leitura, use AQS para aumentar os 16 bits de estado mais altos em 1 e retornar verdadeiro.

boolean tryLock (longo tempo limite, unidade TimeUnit)

Semelhante a tryLock, a diferença é que o período de tempo limite é definido e o período de tempo limite acabou.Se o bloqueio de leitura não for lido, false é retornado diretamente.

A resposta pode ser interrompida.Quando outros encadeamentos chamam o método interrupt () do encadeamento para interromper o encadeamento atual, o encadeamento atual lança InterruptedException.

Ler liberação de bloqueio

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




Compreensão da degradação do bloqueio

O downgrade de bloqueio significa que um bloqueio de gravação pode ser rebaixado para um bloqueio de leitura , mas a sequência de aquisição do bloqueio de gravação, aquisição do bloqueio de leitura e liberação do bloqueio de gravação deve ser seguida . Observe que se o encadeamento atual primeiro adquirir o bloqueio de gravação, depois liberar o bloqueio de gravação e, em seguida, adquirir o bloqueio de leitura, este processo não pode ser chamado de degradação de bloqueio. A degradação de bloqueio deve seguir essa ordem.

Observe que o autor Doug Lea não disse que os bloqueios de gravação são mais avançados. Se um encadeamento contém um bloqueio de leitura, a aquisição do bloqueio de gravação também precisa esperar, mas pode ser visto no código-fonte que os bloqueios de gravação recebem um cuidado especial, como no modo injusto Para melhorar o rendimento, se for descoberto que o primeiro nó é o encadeamento que adquire o bloqueio de gravação, a aquisição é bem-sucedida diretamente.

A parte do downgrade do bloqueio é refletida no código-fonte:

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

É necessário adquirir um bloqueio de leitura durante a degradação do bloqueio?

Se o encadeamento atual A não adquirir o bloqueio de leitura, mas libera diretamente o bloqueio de gravação, neste momento outro encadeamento B adquire o bloqueio de gravação, então a modificação de dados deste encadeamento B não será visível para o encadeamento atual A. Se um bloqueio de leitura é adquirido, o thread B julga que se houver um bloqueio de leitura que não foi liberado durante o processo de aquisição de um bloqueio de gravação, ele será bloqueado. Somente após o thread A atual liberar o bloqueio de leitura, o thread B irá adquirir com sucesso o bloqueio de escrita.

Resumindo

  • A camada inferior de ReentrantReadWriteLock é implementada usando AQS. Os 16 bits superiores do valor de status AQS indicam o número de bloqueios de leitura adquiridos e os 16 bits inferiores identificam o número de threads reentrantes que adquiriram o bloqueio de gravação. O CAS é operado para conseguir a separação de leitura e gravação., É adequado para cenários em que leia mais e escreva menos.
  • Três recursos do ReentrantReadWriteLock: Equidade: Suporta os modos justo e injusto. Reentrância: Suporta reentrada, bloqueios de leitura e gravação suportam até 65535. Downgrade de bloqueio: obtenha primeiro o bloqueio de gravação, depois adquira o bloqueio de leitura e, em seguida, libere o bloqueio de gravação, o bloqueio de gravação pode ser rebaixado para um bloqueio de leitura.
  • Bloqueio de leitura e gravação: O bloqueio de leitura e gravação permite que vários threads de leitura acessem ao mesmo tempo, mas quando o thread de gravação acessa, todos os threads de leitura e outros threads de gravação são bloqueados.

Link original: https://www.cnblogs.com/summerday152/p/14284646.html

Se você acha que este artigo é útil para você, pode seguir minha conta oficial e responder à palavra-chave [Entrevista] para obter uma compilação de pontos de conhecimento do núcleo de Java e um pacote de presente para entrevista! Existem mais artigos técnicos e materiais relacionados para compartilhar. Deixe que todos aprendam e progridam juntos!

Acho que você gosta

Origin blog.csdn.net/weixin_48182198/article/details/112775206
Recomendado
Clasificación