El diseño e implementación de bloqueos de lectura-escritura en Java, no sé cómo entrar

Para el escenario de más lectura y menos escritura, Java proporciona otro bloqueo de lectura-escritura ReentrantReadWriteLock (RRW) que implementa la interfaz Lock. Se ha analizado que ReentrantLock es un bloqueo exclusivo y solo se permite acceder a un hilo al mismo tiempo.

RRW permite el acceso simultáneo de varios hilos del lector, pero no permite el acceso simultáneo del hilo del escritor y del hilo del lector, y del hilo del escritor y del hilo del escritor.

El bloqueo de lectura y escritura mantiene dos bloqueos internamente, uno es ReadLock para operaciones de lectura y el otro es WriteLock para operaciones de escritura.

Los bloqueos de lectura y escritura cumplen con los siguientes tres principios básicos

Varios subprocesos
pueden leer variables compartidas al mismo tiempo; solo un subproceso puede escribir variables compartidas;
si un subproceso de escritura está realizando una operación de escritura, el subproceso lector no puede leer variables compartidas en este momento.

Cómo implementar el bloqueo de lectura y escritura

RRW también se implementa en base a AQS, y su sincronizador personalizado (heredado de AQS) necesita mantener el estado de varios subprocesos de lectura y un subproceso de escritor en el estado de estado de sincronización. El método de RRW es utilizar los bits altos y bajos para lograr un control de configuración de dos estados, un int ocupa 4 bytes y un byte 8 bits. Entonces, los 16 bits superiores representan lectura y los 16 bits inferiores representan escritura.

abstract static class Sync extends AbstractQueuedSynchronizer {

  static final int SHARED_SHIFT   = 16;

  // 10000000000000000(65536)
  static final int SHARED_UNIT    = (1 << SHARED_SHIFT);

  // 65535
  static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;

  //1111111111111111
  static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

  // 读锁(共享锁)的数量,只计算高16位的值
  static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

  // 写锁(独占锁)的数量
  static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
 }

Adquirir un bloqueo de lectura

Cuando un hilo adquiere un bloqueo de lectura, primero juzga los 16 bits bajos del estado de sincronización. Si hay un bloqueo de escritura, no logra adquirir el bloqueo y entra en la cola CLH para bloquear. De lo contrario, determina si el hilo actual debe Si no se debe bloquear, intenta el estado de sincronización CAS El bloqueo de sincronización se actualiza correctamente para leer el estado.

protected final int tryAcquireShared(int unused) {
           
  Thread current = Thread.currentThread();
  int c = getState();
  // 如果当前已经有写锁了,则获取失败
  if (exclusiveCount(c) != 0 &&
      getExclusiveOwnerThread() != current)
      return -1;
  // 获取读锁数量
  int r = sharedCount(c);

  // 非公平锁实现中readerShouldBlock()返回true表示CLH队列中有正在排队的写锁
  // CAS设置读锁的状态值
  if (!readerShouldBlock() &&
      r < MAX_COUNT &&
      compareAndSetState(c, c + SHARED_UNIT)) {

      // 省略记录获取readLock次数的代码

      return 1;
  }

  // 针对上面失败的条件进行再次处理
  return fullTryAcquireShared(current);
}

final int fullTryAcquireShared(Thread current) {
  
  // 无线循环
  for (;;) {
    int c = getState();
    if (exclusiveCount(c) != 0) {
      // 如果不是当前线程持有写锁,则进入CLH队列阻塞
      if (getExclusiveOwnerThread() != current)
        return -1;
    } 

    // 如果reader应该被阻塞
    else if (readerShouldBlock()) {
        // Make sure we're not acquiring read lock reentrantly
        if (firstReader == current) {
            // assert firstReaderHoldCount > 0;
        } else {
            if (rh == null) {
                rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current)) {
                    rh = readHolds.get();
                    if (rh.count == 0)
                        readHolds.remove();
                }
            }
            // 当前线程没有持有读锁,即不存在锁重入情况。则进入CLH队列阻塞
            if (rh.count == 0)
                return -1;
        }
    }

    // 共享锁的如果超出了限制
    if (sharedCount(c) == MAX_COUNT)
        throw new Error("Maximum lock count exceeded");

    // CAS设置状态值
    if (compareAndSetState(c, c + SHARED_UNIT)) {
      
      // 省略记录readLock次数的代码

      return 1;
    }
  }

}

El valor de SHARED_UNIT es 65536, lo que significa que cuando se adquiere el bloqueo de lectura por primera vez, el valor del estado se convierte en 65536.

En la implementación del bloqueo justo, cuando hay subprocesos en la cola de CLH, el método readerShouldBlock () devolverá verdadero. En la implementación de bloqueos injustos, cuando hay un subproceso esperando adquirir un bloqueo de escritura en la cola CLH, devuelve verdadero.

También debe tenerse en cuenta que al adquirir el bloqueo de lectura, si el hilo actual ya tiene el bloqueo de escritura, el bloqueo de lectura aún se puede adquirir con éxito. La degradación del bloqueo se mencionará más adelante. Si tiene alguna pregunta sobre el código, puede consultar el código para solicitar el bloqueo aquí.

Liberar bloqueo de lectura

protected final boolean tryReleaseShared(int unused) {
           
  for (;;) {
    int c = getState();
    // 减去65536
    int nextc = c - SHARED_UNIT;
    // 只有当state的值变成0才会真正的释放锁
    if (compareAndSetState(c, nextc))
        return nextc == 0;
}
}

Cuando se libera el bloqueo, el valor de estado debe restarse de 65536, porque cuando se adquiere el bloqueo de lectura por primera vez, el valor de estado se convierte en 65536.

Cuando cualquier subproceso libera el bloqueo de lectura, el bloqueo se libera realmente solo cuando el estado == 0. Por ejemplo, si 100 subprocesos adquieren el bloqueo de lectura, solo el último subproceso ejecuta el método tryReleaseShared para liberar realmente el bloqueo, y se activará en este momento El subproceso en cola de la cola CLH.

Adquirir bloqueo de escritura

Cuando un hilo intenta adquirir un bloqueo de escritura, primero determinará si el estado del estado de sincronización es 0. Si el estado es igual a 0, significa que ningún otro subproceso ha adquirido el bloqueo por el momento; si el estado no es igual a 0, significa que otros subprocesos han adquirido el bloqueo.

En este momento, se juzga si los 16 bits inferiores (w) del estado son 0. Si w es 0, significa que otros subprocesos han adquirido el bloqueo de lectura y entran en la cola CLH para bloqueo en espera.

Si w no es 0, significa que otros subprocesos han adquirido el bloqueo de escritura. En este momento, debe determinar si el bloqueo de escritura es el subproceso actual. De lo contrario, ingrese a la cola CLH y espere el bloqueo. Si el bloqueo de escritura es el subproceso actual, luego determina si el subproceso actual ha adquirido el bloqueo de escritura más que el número máximo de veces. Si lo supera, lanza una excepción. De lo contrario, actualice el estado de sincronización.

// 获取写锁
protected final boolean tryAcquire(int acquires) {
           
  Thread current = Thread.currentThread();
  int c = getState();
  int w = exclusiveCount(c);

  // 判断state是否为0
  if (c != 0) {
      // 获取锁失败
      if (w == 0 || current != getExclusiveOwnerThread())
          return false;

      // 判断当前线程获取写锁是否超出了最大次数65535
      if (w + exclusiveCount(acquires) > MAX_COUNT)
          throw new Error("Maximum lock count exceeded");
      
      // 锁重入
      setState(c + acquires);
      return true;
  }
  // 非公平锁实现中writerShouldBlock()永远返回为false
  // CAS修改state的值
  if (writerShouldBlock() ||
      !compareAndSetState(c, c + acquires))
      return false;

  // CAS成功后,设置当前线程为拥有独占锁的线程
  setExclusiveOwnerThread(current);
  return true;
}

En la implementación del bloqueo justo, cuando hay subprocesos en cola en la cola CLH, el método writerShouldBlock () devolverá verdadero y el subproceso que adquiere el bloqueo de escritura se bloqueará.

Liberar bloqueo de escritura

La lógica para liberar el bloqueo de escritura es relativamente simple

protected final boolean tryRelease(int releases) {
  // 写锁是否被当前线程持有
  if (!isHeldExclusively())
      throw new IllegalMonitorStateException();
  
  int nextc = getState() - releases;
  boolean free = exclusiveCount(nextc) == 0;

  // 没有其他线程持有写锁
  if (free)
      setExclusiveOwnerThread(null);
  setState(nextc);
  return free;
}
锁的升级?
// 准备读缓存
readLock.lock();
try {
  v = map.get(key);
  if(v == null) {
    writeLock.lock();
    try {
      if(map.get(key) != null) {
        return map.get(key);
      }

      // 更新缓存代码,省略
    } finally {
      writeLock.unlock();
    }
  }
} finally {
  readLock.unlock();
}

Para el código para obtener datos en caché anterior (este es también el escenario de aplicación de RRW), primero obtenga un bloqueo de lectura y luego actualice a un bloqueo de escritura Este comportamiento se denomina actualización de bloqueo. Desafortunadamente, RRW no lo admite. Esto hará que el bloqueo de escritura espere para siempre y, finalmente, el hilo se bloquee permanentemente. Por lo tanto, no se permite la actualización de la cerradura.

Bloquear degradación

Aunque no se permite la actualización de la cerradura, es posible la degradación de la cerradura.

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

ReadLock readLock = lock.readLock();

WriteLock writeLock = lock.writeLock();

Map<String, String> dataMap = new HashMap();

public void processCacheData() {
  readLock.lock();

  if(!cacheValid()) {

    // 释放读锁,因为不允许
    readLock.unlock();

    writeLock.lock();

    try {
      if(!cacheValid()) {
          dataMap.put("key", "think123");
      }

      // 降级为读锁
      readLock.lock();
    } finally {
        writeLock.unlock();
    }
  }

  try {
    // 仍然持有读锁
    System.out.println(dataMap);
  } finally {
      readLock.unlock();
  }
}

public boolean cacheValid() {
    return !dataMap.isEmpty();
}

RRW necesita atención

En el caso de muchas lecturas y pocas escrituras, RRW hará que el hilo de escritura sufra inanición (inanición), lo que significa que el hilo de escritura estará en un estado de espera porque no puede competir por el bloqueo.
Los bloqueos de escritura admiten variables de condición, pero los bloqueos de lectura no. La llamada de bloqueo de lectura newCondition () arrojará UnsupportedOperationException

Supongo que te gusta

Origin blog.csdn.net/doubututou/article/details/111317098
Recomendado
Clasificación