[Herramienta simultánea de Java clase-exclusión mutua] ReadWriteLock (bloqueo de lectura-escritura)


Tal escenario a menudo se encuentra en el desarrollo: Leer más y escribir menos Por ejemplo, el caché puede mejorar el rendimiento. Una condición importante es que los datos almacenados en caché deben leerse más y menos escritos, y los datos en el caché básicamente no cambiarán (escribirán menos). Lea los datos almacenados en caché con frecuencia (lea Más)

Para este escenario, el paquete concurrente de Java SDK proporciona un bloqueo de lectura-escritura-ReadWriteLock, y un bloqueo más rápido que el bloqueo de lectura-escritura-StampedLock.

1.ReadWriteLock (bloqueo de lectura-escritura)

1.2 ¿Qué es un bloqueo de lectura-escritura?

Los bloqueos de lectura y escritura siguen estas tres reglas:

  • Permitir múltiples hilos para leer variables compartidas al mismo tiempo;
  • Solo un hilo puede escribir variables compartidas;
  • Si un hilo realiza una operación de escritura, otros hilos tienen prohibido leer variables compartidas en este momento.

La diferencia entre un bloqueo de lectura y escritura y un bloqueo de exclusión mutua es que un bloqueo de lectura y escritura permite que varios subprocesos realicen operaciones de lectura.
El mismo punto es: cuando el bloqueo de lectura-escritura está escribiendo, no permite que otros hilos lean o escriban.

1.3 ¿Usar bloqueo de lectura-escritura para implementar rápidamente un caché?

Para implementar el almacenamiento en caché, proporcionamos dos métodos, el almacenamiento en caché de lectura (método get ()) y el almacenamiento en caché de escritura (método put ()). Aquí está nuestro código:

class Cache<K,V> {
 final Map<K, V> m=new HashMap<>();//final域禁止写final域引用对象以及第一次写引用对象成员域到构造函数外,之前文章提到过
 final ReadWriteLock rwl =new ReentrantReadWriteLock();
 final Lock r = rwl.readLock();  // 读锁
 final Lock w = rwl.writeLock(); // 写锁
  V get(K key) { // 读缓存
    r.lock();
    try { return m.get(key); }
    finally { r.unlock(); }
  }
  V put(K key, V value) {  // 写缓存
    w.lock();
    try { return m.put(key, v); }
    finally { w.unlock(); }
  }
}

Implementemos el caché a continuación:

  1. Si desea implementar el almacenamiento en caché, primero debe resolver el problema de la inicialización del caché. Dos formas: 1. Cargar datos a la vez 2. Cargar datos bajo demanda La carga bajo demanda de la
    Inserte la descripción de la imagen aquí
    caché se implementa a continuación: En la caché de consulta, los datos se consultan desde la base de datos a la caché si no existe (la escritura de caché debe ser juzgada nuevamente por otro Hilo escribió),
class Cache<K,V> {
  final Map<K, V> m =new HashMap<>();
  final ReadWriteLock rwl = new ReentrantReadWriteLock();
  final Lock r = rwl.readLock();
  final Lock w = rwl.writeLock(); 
  V get(K key) {
    V v = null;
    r.lock(); //读缓存         ①
    try {
      v = m.get(key);} finally{
      r.unlock();}
    //缓存中存在,返回
    if(v != null) {return v;
    }  
    //缓存中不存在,查询数据库
    w.lock();try {
      //再次验证
      //其他线程可能已经查询过数据库
      v = m.get(key);if(v == null){//查询数据库
        v=省略代码无数
        m.put(key, v);
      }
    } finally{
      w.unlock();
    }
    return v; 
  }
}
  1. Además, la sincronización de los datos en caché y los datos de origen también debe resolverse, y los dos deben garantizar la coherencia.
    Solución: 1. Mecanismo de tiempo de espera: cuando los datos en caché exceden el límite de tiempo, los datos se invalidan en el caché, esperando otro acceso para cargar los datos de origen en el caché.
    2. O cuando se modifican los datos de origen, retroalimente rápidamente al caché y, si se produce la modificación, almacene los últimos datos en el caché.
    3. Solución de doble escritura para base de datos y caché.
1.4 Actualización y degradación del bloqueo de lectura-escritura
1.4.1 ¡ReadWriteLock no puede actualizarse!

Primero mira un código de ejemplo:

r.lock();//读缓存
try {
  v = m.get(key);if (v == null) {
    w.lock();
    try {   
      //省略详细代码 //再次验证并更新缓存
    } finally{
      w.unlock();
    }
  }
} finally{
  r.unlock();}

En lo anterior 1. Adquiera primero el bloqueo de lectura y luego vuelva a adquirir el bloqueo de escritura, esto se llama actualización de bloqueo , pero ** ReadWriteLock no admite esta actualización. ** En el código anterior, el bloqueo de lectura no se ha liberado, y luego para obtener el bloqueo de escritura, hará que el bloqueo de escritura espere para siempre, y luego bloqueará el hilo, y el servidor puede mostrar una baja utilización de la CPU.

1.4.1 ReadWriteLock permite la degradación

La siguiente es otra forma de lograr la carga bajo demanda de datos en caché, directamente en el código:

class CachedData {
  Object data;
  volatile boolean cacheValid;
  final ReadWriteLock rwl =new ReentrantReadWriteLock();
  final Lock r = rwl.readLock(); // 读锁 
  final Lock w = rwl.writeLock();  //写锁 
  void processCachedData() {
    r.lock();   // 获取读锁
    if (!cacheValid) { //先判读缓存中是否存在该数据,如果存在跳过if下面的方法,直接use(data)然后释放读锁。
    //否则需要写入缓存数据,获取写锁进行写操作,因为不允许锁的升级,所以需要先释放读锁,获取写锁,把数据写入缓存
      r.unlock(); // 释放读锁,因为不允许读锁的升级     
      w.lock(); // 获取写锁
      try {
        // 获取写锁之前有可能其他线程获取写锁写入了,所以再次检查状态  
        if (!cacheValid) {//如果没有写入缓存,就写入
          data = ...
          cacheValid = true;
        }
        // 因为下面需要释放读锁,我们需要再获得读锁,让其有东西释放,同时允许锁降级,所以直接获得读锁。
        r.lock();  // 释放写锁前,降级为读锁,这个锁也需要释放
      } finally {
        // 释放写锁
        w.unlock(); 
      }
    }
    // 此处仍然持有读锁
    try {use(data);} 
    finally {r.unlock();}//释放读锁
  }
}
1.5 Notas sobre el bloqueo de lectura-escritura
  1. ReadWriteLock es una interfaz y su clase de implementación es ReentrantReadWriteLock, por el nombre se puede ver que es reentrante.
  2. Al mismo tiempo, los bloqueos de lectura y escritura que adquiere implementan la interfaz de bloqueo, por lo que además de admitir el método lock (), también admite bloqueos de adquisición sin bloqueo tryLock (), lockInterruptibly () y otros métodos.
  3. El bloqueo de lectura y escritura es similar a ReentrantLock, y también admite el modo justo y el modo injusto.
  4. Nota: Su bloqueo de escritura admite variables condicionales, pero los bloqueos de lectura no admiten variables condicionales. La lectura de bloqueo llama a newCondition () arrojará una excepción.

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/105111777
Recomendado
Clasificación