[Herramienta de concurrencia Java clase-exclusión mutua] StampedLock (un bloqueo con mayor rendimiento que el bloqueo de lectura-escritura)

2. StampedLock

2.1 Tres modos de bloqueo compatibles con StampedLock

Los tres modos son: bloqueo de escritura, bloqueo de lectura pesimista y lectura optimista .

  • Entre ellos, su bloqueo de escritura, bloqueo de lectura pesimista y bloqueo de escritura ReadWriteLock, la semántica de bloqueo de lectura son similares: ambos permiten que varios hilos obtengan bloqueos de lectura pesimista al mismo tiempo, solo permiten que un hilo obtenga bloqueos de escritura, mientras que los bloqueos de escritura y lectura son mutuamente excluyentes De La diferencia es que: el bloqueo de escritura en StampedLock, el bloqueo de lectura pesimista devolverá un sello después de que el bloqueo se haya bloqueado correctamente; luego, al desbloquear, debe pasar este sello. El código relevante es el siguiente:
final StampedLock sl =  new StampedLock();  
long stamp = sl.readLock();		// 获取/释放悲观读锁示意代码
try {
  //省略业务相关代码
} finally {
  sl.unlockRead(stamp);
}

long stamp = sl.writeLock();// 获取/释放写锁示意代码
try {
  //省略业务相关代码
} finally {
  sl.unlockWrite(stamp);
}
  • Lectura optimista: ¡La razón por la cual StampedLock es mejor que ReadWriteLock es que la clave es que StampedLock es compatible con la lectura optimista (las operaciones de lectura optimista no tienen bloqueo)!
    ReadWriteLock admite la lectura de múltiples hilos, pero cuando se leen múltiples hilos, se bloquean todos los hilos de escritura, y la lectura optimista proporcionada por StampedLock, cuando se leen varios hilos, permite que un hilo obtenga un bloqueo de escritura.
2.2 Principio StampedLock de lectura optimista

El siguiente código es un ejemplo oficial del SDK de Java y se ha modificado ligeramente.

class Point {
  private int x, y;
  final StampedLock sl = new StampedLock();
  int distanceFromOrigin() { //计算到原点的距离  
    // 乐观读
    long stamp = sl.tryOptimisticRead();  
    int curX = x, curY = y; // 读入局部变量, 读的过程数据可能被修改
    //判断执行读操作期间,是否存在写操作,如果存在,则sl.validate返回false
    if (!sl.validate(stamp)){
      // 升级为悲观读锁,
      stamp = sl.readLock();
      try {
        curX = x;
        curY = y;
      } finally {
        //释放悲观读锁
        sl.unlockRead(stamp);
      }
    }
    return Math.sqrt(
      curX * curX + curY * curY);
  }
}

En el código anterior, si hay una operación de escritura durante la lectura optimista, la lectura optimista se actualizará a un bloqueo de lectura pesimista, de lo contrario se requerirá un bucle para ejecutar repetidamente la lectura optimista hasta que no haya una operación de escritura durante el período, el bucle desperdiciará recursos de la CPU, por lo que la actualización es pesimista Bloqueo, razonable!

Nota : En el código anterior, no hay operación de escritura durante la verificación de validación, pero si la operación de escritura se realiza antes de Math.sqrt y después de la verificación, la sincronización de los datos no se puede juzgar a tiempo, y solo se puede garantizar la exactitud y consistencia. Esto es algo que no entiendo. Si algún maestro lo comprende, espero escribir un mensaje en el área de comentarios.

De hecho, cuando se trata de una lectura optimista, puede pensar en la transacción de la base de datos MVCC (control de concurrencia de múltiples versiones). De hecho, los dos son algo similares. De acuerdo con el principio de MVCC, cada modificación de los datos corresponde a un número de versión. No solo hay modificación de los datos o número de versión. .
Cuando se inicia la transacción de la base de datos, se tomará una instantánea de la base de datos, y todas las operaciones de lectura y escritura de la transacción se basarán en esta instantánea en el futuro. Cuando se confirme la transacción, los datos de la base de datos se escribirán en ese momento y se verificará la escritura. Si la versión de los datos de entrada es consistente con el número de versión al leer los datos. Si son consistentes, es decir, todos los datos leídos y escritos no han cambiado durante la ejecución de la transacción, y los datos son enviados. Si son inconsistentes, han cambiado, lo que indica que durante la ejecución de la transacción, Se enviaron otras transacciones, se produjeron conflictos y no se pudieron enviar.

La idea de la lectura optimista es que nada está mal, no hay operación de escritura, verifique si hay una operación de escritura, no, opere los datos; sí, actualice el bloqueo pesimista.
La idea de MVCC es que nada está mal, no hay operación de escritura, hacer una copia, modificar la copia primero, al escribir en la base de datos, (verifique la versión para ver si hay otras operaciones de escritura, sí, no se pueden comprometer transacciones, no, comprometer transacciones). Operaciones atómicas

2.3 Consideraciones de StampedLock

El rendimiento de StampedLock es muy bueno para el escenario de leer más y escribir menos. Los escenarios de aplicación simples básicamente pueden reemplazar a ReadWriteLock, pero la función de StampedLock es solo un subconjunto de ReadWriteLock. Cuando se usa, todavía hay algunos lugares para tener en cuenta.

  1. StampedLock no agrega Reentrant en su denominación, por lo que StampedLock no admite la reentrada.
  2. Además, los bloqueos de lectura y escritura pesimistas de StampedLock no admiten variables de condición, esto también requiere su atención.
  3. Si el hilo está bloqueado en el readLock () o writeLock () de StampedLock, llamar al método interrupt () del hilo bloqueado en este momento hará que la CPU se dispare. Por lo tanto, cuando utilice StampedLock, no debe llamar a operaciones de interrupción. Si necesita admitir la función de interrupción, debe usar el bloqueo de lectura pesimista interrumpible readLockInterruptibly () y el bloqueo de escritura writeLockInterruptibly (). Esta regla debe recordarse claramente.
    Por ejemplo, el siguiente ejemplo de código (la interrupción del bloqueo de readLock hace que la CPU se dispare):
final StampedLock lock = new StampedLock();
Thread T1 = new Thread(()->{
  // 获取写锁
  lock.writeLock();
  LockSupport.park(); // 永远阻塞在此处,不释放写锁
});
T1.start();
// 保证T1获取写锁
Thread.sleep(100);
Thread T2 = new Thread(()->
  lock.readLock()  //阻塞在悲观读锁
);
T2.start();
Thread.sleep(100);// 保证T2阻塞在读锁
T2.interrupt();//中断线程T2//会导致线程T2所在CPU飙升
T2.join();
2.4 Plantilla de lectura y escritura StampedLock estándar

Se recomienda que utilice StampedLock de acuerdo con la siguiente plantilla en su trabajo real.

  • Plantilla de lectura de StampedLock:
final StampedLock sl =  new StampedLock();
// 乐观读
long stamp =  sl.tryOptimisticRead();
......// 读入方法局部变量
// 校验stamp
if (!sl.validate(stamp)){
  // 升级为悲观读锁
  stamp = sl.readLock();
  try {   
    ..... // 读入方法局部变量
  } finally {
    //释放悲观读锁
    sl.unlockRead(stamp);
  }
}
//使用方法局部变量执行业务操作
......
  • Plantilla de escritura StampedLock:
long stamp = sl.writeLock();
try {
  ......// 写共享变量
} finally {
  sl.unlockWrite(stamp);
}

Referencia: Geek Time
Más: Deng Xin

Publicado 34 artículos originales · Me gusta0 · Visitas 1089

Supongo que te gusta

Origin blog.csdn.net/qq_42634696/article/details/105135368
Recomendado
Clasificación