Análisis de bloqueo de lectura y escritura ReentrantReadWriteLock

Descripción general de ReadWriteLock

Como dijimos antes, ReentrantLock es un bloqueo exclusivo. Solo un hilo puede adquirir el bloqueo a la vez. De hecho, habrá muchos escenarios donde leer más y menos escribir , y la operación de lectura en sí no tendrá problemas de competencia de datos. utiliza un bloqueo exclusivo. Puede hacer que uno de los hilos del lector espere otros hilos del lector, lo que reduce el rendimiento.

En respuesta a este escenario en el que leer más y escribir menos, surgieron los bloqueos de lectura y escritura. El bloqueo de lectura y escritura permite que varios subprocesos del lector accedan al mismo tiempo, pero cuando el subproceso del escritor accede, todos los subprocesos del lector y otros subprocesos del escritor se bloquean. Echemos un vistazo a la interfaz de nivel superior de bloqueos de lectura y escritura en Java, que se encuentra en el paquete: java.util.concurrent.locks:

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

Creo que comprenderá de inmediato que el bloqueo de lectura y escritura en realidad mantiene un par de bloqueos, un bloqueo de escritura y un bloqueo de lectura. A través de la estrategia de separación de lectura y escritura, se permite que varios subprocesos adquieran bloqueos de lectura al mismo tiempo. , mejorando enormemente la concurrencia.

Caso de bloqueo de lectura y escritura

El documento JavaDoc es muy detallado y nos da un ejemplo del uso de ReentrantReadWriteLock. Echemos un vistazo directamente:

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 un poco, en detalle, en la siguiente parte de análisis:

Los bloqueos de lectura y escritura ReentrantReadWriteLock se dividen en bloqueos de lectura y bloqueos de escritura. Los bloqueos de lectura son bloqueos compartidos y los bloqueos de escritura son bloqueos exclusivos. El subproceso que mantiene el bloqueo de escritura puede continuar adquiriendo el bloqueo de lectura, lo que se denomina degradación de bloqueo.

Descripción general de la arquitectura ReentrantReadWriteLock

ReentrantReadWriteLock es la implementación de ReadWriteLock. De hecho, viendo el nombre: bloqueo de lectura-escritura reentrante, probablemente podamos adivinar su significado. Además de implementar los métodos readLock () y writeLock (), también proporciona algunos métodos importantes, que analizaremos más adelante.

Análisis de bloqueo de lectura y escritura 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等方法
    }

Resumamos:

  • ReentrantReadWriteLock mantiene dos clases internas, ReadLock y WriteLock, las cuales confían a Sync para implementar funciones específicas [Sync es la implementación de AQS, lo que dije antes es muy claro].
  • Al igual que ReentrantLock, también proporciona dos implementaciones de equidad e injusticia: FairSync y NonfairSync. Son clases de implementación de Sync. Para conocer las dos diferencias, consulte: Diferencias entre estrategias justas e injustas
  • Las instancias de ReadLock y WriteLock se pueden obtener mediante dos métodos: readLock () y writeLock ().
  • ReadLock usa el modo compartido y WriteLock usa el modo exclusivo Para conocer la diferencia entre los dos, consulte: Serie de aprendizaje del código fuente del paquete concurrente de Java: La diferencia entre la adquisición y liberación de recursos compartidos y exclusivos de AQS.

Sincronizar campos importantes y representación de clases internas

Como mencionamos al aprender AQS, la clave de AQS es el estado del campo de estado de sincronización. Por ejemplo, tomando ReentrantLock como ejemplo, su estado es 0 para indicar que el candado está libre, 1 para indicar que el candado está adquirido y mayor que 1 para indicar que el candado está siendo utilizado por el mismo hilo.

Sin embargo, se sabe que el bloqueo de lectura-escritura necesita mantener dos estados, ¿cómo representarlo con un solo estado de variable entera? El bloqueo de lectura y escritura utiliza la idea de corte bit a bit para dividir inteligentemente el estado en dos partes:

  • High 16 bits: indica el estado de lectura, que representa el número de adquisiciones de bloqueo de lectura [incluido el número de reentrantes]. Debido al modo de intercambio, varios subprocesos pueden adquirir el bloqueo y reentrar.
  • El bit 16: Representa el estado de escritura, representando el número de bloqueos de escritura reentrantes En modo exclusivo, solo un hilo puede obtener el bloqueo de escritura, pero puede representar el número de reentrantes.

Note la diferencia entre los dos.

/*
         * 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 sin signo y desplazamiento a la derecha en 16 bits, de hecho, es para obtener los 16 bits más altos.
  • ExclusiveCount: Borrar los 16 bits altos es en realidad obtener los 16 bits bajos.
// 记录每个线程持有的读锁数量
		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
        }

Adquisición de bloqueo de escritura

WriteLock implementa el bloqueo de escritura en ReentrantReadWriteLock.

bloqueo de vacío ()

El bloqueo de escritura es un bloqueo exclusivo y solo un hilo puede adquirir el bloqueo a la vez.

  • Si ningún subproceso adquiere actualmente el bloqueo de lectura y el bloqueo de escritura, el subproceso actual puede adquirir el bloqueo de escritura y regresar.
  • Si el hilo actual ha adquirido el bloqueo de lectura y el bloqueo de escritura, el hilo que solicita actualmente el bloqueo de escritura se bloqueará y suspenderá.

El bloqueo de escritura es un bloqueo reentrante. Si el hilo actual ya ha adquirido el bloqueo, adquirirlo de nuevo simplemente devuelve el número de veces reentrante +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;
        }
    }

escritor booleanoShouldBlock ()

Se implementa el método writerShouldBlock, hay una diferencia entre imparcialidad e injusticia:

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 bloqueos injustos, este método siempre devolverá falso, lo que significa que irá directamente al paso compareAndSetState (c, c + adquiere), intentará adquirir el bloqueo de escritura a través de CAS y establecerá el estado después de la adquisición. es correcto. El hilo actual se establecerá como el titular del bloqueo, y se devolverá falso en caso de error.

Significa: en modo injusto, intentará directamente cas para agarrar el bloqueo de escritura, y luego hará cola si no se puede agarrar; para modo justo, si el hilo actual tiene un nodo precursor en la cola de bloqueo, dará hasta el proceso de CAS compitiendo por el bloqueo de escritura.

bloqueo anulado Interrumpidamente ()

De manera similar al método lockInterruptbly () de ReentrantLock, cuando otros hilos llaman al método interrupt () del hilo para interrumpir el hilo actual, el hilo actual lanzará una 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 booleano ()

Intente adquirir un bloqueo de escritura. Si ningún otro subproceso tiene actualmente un bloqueo de escritura o un bloqueo de lectura, el subproceso actual adquirirá correctamente el bloqueo de escritura y devolverá verdadero.

Si actualmente hay otros subprocesos que tienen un bloqueo de escritura o un bloqueo de lectura, este método devuelve directamente falso y el subproceso actual no se bloqueará.

Si el hilo actual ya tiene el bloqueo de escritura, simplemente aumente el valor de estado de AQS y devuelva verdadero directamente.

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

De hecho, la lógica del método de bloqueo no es muy diferente, pero se adopta la lógica de bloqueo injusta del método de bloqueo.

boolean tryLock (tiempo de espera largo, unidad TimeUnit)

Similar al método tryLock (tiempo de espera prolongado, unidad TimeUnit) de ReentrantLock.

Intente adquirir el bloqueo de escritura. Si la adquisición falla, el hilo actual se suspenderá durante un tiempo especificado. Una vez transcurrido el tiempo, se activará el hilo actual. Si el bloqueo aún no se adquiere, se devolverá falso.

Además, este método responderá a las interrupciones. Si otros hilos llaman al método interrupt () del hilo actual, responde a la interrupción y lanza una excepción.

Desbloqueo de escritura

desbloqueo vacío ()

Intente liberar el bloqueo. Si el hilo actual mantiene el bloqueo, llamar a este método disminuirá el estado de AQS mantenido por el hilo en 1; si el valor del estado actual es 0 después de disminuir 1, el hilo actual liberará el bloqueo.

Si el hilo actual no mantiene el bloqueo y llama a este método, se lanza una 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;
        }
    }


Adquisición de bloqueo de lectura

ReadLock implementa el bloqueo de lectura en ReentrantReadWriteLock.ps: La adquisición y liberación de bloqueos de lectura es más complicada que los bloqueos de escritura.

bloqueo de vacío ()

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

lector booleanoShouldBlock ()

Se implementa el método readerShouldBlock, hay una diferencia entre equidad e injusticia:

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

Mire específicamente la implementación de bloqueos injustos, aparentemente el método FirstQueuedIsExclusive:

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

Conéctese y explique:

  1. ! readerShouldBlock (): Si hay un elemento en la cola, determine si el primer elemento está tratando de adquirir un bloqueo de escritura. Si es así, deje que este elemento vaya primero, y su prioridad es alta.
  2. r <MAX_COUNT: determina si el hilo que actualmente adquiere el bloqueo de lectura alcanza el valor máximo.
  3. compareAndSetState (c, c + SHARED_UNIT): Realice la operación CAS para aumentar el valor alto de 16 bits del valor del estado AQS en 1 .

De hecho: vea si el primer nodo en la cola está tratando de adquirir el bloqueo de escritura , si es así, déjelo ir primero. Si está adquiriendo un bloqueo de lectura, lo siento, vaya a CAS obedientemente y vea quién puede obtenerlo.

¿Qué sucede si no se adquiere el bloqueo de lectura? Ingrese la lógica completa TryAcquireShared para ver:

int fullTryAcquireShared (hilo)

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

Los siguientes métodos de bloqueo de lectura y bloqueo de escritura en realidad no son muy diferentes, el código fuente no se publicará y los amigos interesados ​​pueden verlo por sí mismos.

bloqueo anulado Interrumpidamente ()

Similar al método lock (), la diferencia es que este método puede interrumpir la respuesta.Cuando otros hilos llaman al método interrupt () del hilo para interrumpir el hilo actual, el hilo actual lanza una InterruptedException.

tryLock booleano ()

Intente leer el bloqueo. Si ningún otro hilo tiene actualmente el bloqueo de escritura, el hilo actual adquirirá el bloqueo de lectura correctamente y devolverá verdadero.

Si hay otros subprocesos que actualmente mantienen el bloqueo de escritura, devolverá falso sin bloquear.

Si el hilo actual ya tiene el bloqueo de lectura, use AQS para aumentar los 16 bits altos de estado en 1 y devuelva verdadero.

boolean tryLock (tiempo de espera largo, unidad TimeUnit)

Similar a tryLock, la diferencia es que el período de tiempo de espera está establecido y el período de tiempo de espera termina. Si el bloqueo de lectura no se lee, se devuelve false directamente.

La respuesta puede ser interrumpida.Cuando otros hilos llaman al método interrupt () del hilo para interrumpir el hilo actual, el hilo actual lanza InterruptedException.

Leer abrepuertas

desbloqueo vacío ()

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




Comprensión de la degradación de las cerraduras

La degradación de bloqueo significa que un bloqueo de escritura se puede degradar a un bloqueo de lectura , pero se debe seguir la secuencia de adquirir el bloqueo de escritura, adquirir el bloqueo de lectura y luego liberar el bloqueo de escritura . Tenga en cuenta que si el subproceso actual primero adquiere el bloqueo de escritura, luego libera el bloqueo de escritura y luego adquiere el bloqueo de lectura, este proceso no se puede llamar degradación de bloqueo. La degradación de bloqueo debe seguir ese orden.

Tenga en cuenta que el autor Doug Lea no dijo que los bloqueos de escritura son más avanzados. Si un hilo tiene un bloqueo de lectura, la adquisición del bloqueo de escritura también debe esperar, pero se puede ver en el código fuente que los bloqueos de escritura reciben un cuidado especial. como en modo injusto Para mejorar el rendimiento, si se encuentra que el primer nodo es el subproceso que adquiere el bloqueo de escritura, la adquisición es exitosa directamente.

La parte de la degradación del bloqueo se refleja en el código fuente:

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

¿Es necesario adquirir un bloqueo de lectura durante la degradación del bloqueo?

Si el subproceso actual A no adquiere el bloqueo de lectura pero libera directamente el bloqueo de escritura, en este momento otro subproceso B adquiere el bloqueo de escritura, entonces la modificación de datos de este subproceso B no será visible para el subproceso actual A. Si se adquiere un bloqueo de lectura, el subproceso B juzga que si hay un bloqueo de lectura que no se ha liberado durante el proceso de adquirir un bloqueo de escritura, se bloqueará. Solo después de que el subproceso actual A libera el bloqueo de lectura, el subproceso B adquirir con éxito el bloqueo de escritura.

para resumir

  • La capa inferior de ReentrantReadWriteLock se implementa mediante AQS. Los 16 bits superiores del valor de estado de AQS indican el número de bloqueos de lectura adquiridos y los 16 bits inferiores identifican el número de subprocesos reentrantes que han adquirido el bloqueo de escritura. El CAS se opera en para lograr la separación de lectura y escritura., Es adecuado para escenarios donde leer más y escribir menos.
  • Tres características de ReentrantReadWriteLock: Equidad: admite modos justos e injustos. Reingreso: admite reingreso, los bloqueos de lectura y escritura admiten hasta 65535. Bloquear degradación: Adquiera primero el bloqueo de escritura, luego adquiera el bloqueo de lectura y luego libere el bloqueo de escritura, el bloqueo de escritura se puede degradar a un bloqueo de lectura.
  • Bloqueo de lectura y escritura: el bloqueo de lectura y escritura permite que varios subprocesos de lectura accedan al mismo tiempo, pero cuando el subproceso de escritura accede, todos los subprocesos de lectura y otros subprocesos de escritura se bloquean.

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

Si cree que este artículo es útil para usted, puede seguir mi cuenta oficial y responder a la palabra clave [Entrevista] para obtener una compilación de los puntos de conocimiento básicos de Java y un paquete de regalo para la entrevista. Hay más artículos técnicos de productos secos y materiales relacionados para compartir, ¡que todos aprendan y progresen juntos!

Supongo que te gusta

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