【Candado】 2 La clasificación de candados más completa

1 Clasificación de cerraduras

La clasificación de las cerraduras se ve desde diferentes ángulos. Un candado puede tener múltiples atributos. Por ejemplo, ReentrantLock es un candado exclusivo y un candado reentrante.

Inserte la descripción de la imagen aquí

2 Bloqueo optimista y pesimista

Los bloqueos pesimistas son bloqueos de sincronización mutuamente excluyentes y los bloqueos optimistas son bloqueos de sincronización no excluyentes entre sí.

2.1 Razones para un bloqueo optimista

El problema de las cerraduras pesimistas: el
bloqueo y la activación traen consigo desventajas en el rendimiento. Un recurso está ocupado por un subproceso y otros subprocesos deben esperar.
Bloqueado permanentemente. Si el subproceso que sostiene el bloqueo está bloqueado permanentemente, como bucles infinitos y puntos muertos y otros problemas de actividad, el subproceso que espera el bloqueo no se ejecutará.
Inversión de prioridad. Cuando un subproceso de baja prioridad adquiere un bloqueo y un subproceso de alta prioridad espera el bloqueo, el subproceso adquiere una prioridad más baja.

2.2 ¿Qué es bloqueo optimista / bloqueo pesimista?

Bloqueo pesimista, pensando que los datos son fácilmente modificados por otros hilos, para asegurar la veracidad de los datos, cada vez que se adquieren y modifican los datos, los datos se bloquean. Por ejemplo, clases sincronizadas y relacionadas con el bloqueo.

Bloqueo optimista, pensando que no habrá interferencia de otros hilos durante el funcionamiento, por lo que no bloquea el objeto operado. Durante la actualización, se juzgará si otros subprocesos lo han modificado durante la modificación. Si no se ha modificado, significa que solo está funcionando el hilo actual y los datos se modifican normalmente. Si los datos han sido modificados por otros subprocesos, detendrá la actualización anterior y seleccionará la estrategia de ejecución, como descartar, informar un error y reintentar.

El bloqueo optimista generalmente se implementa mediante el algoritmo CAS. Por ejemplo, clases atómicas, contenedores concurrentes.

// 悲观锁
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    
    
    return threadInitNumber++;
}
// 乐观锁
private AtomicInteger realIndex = new AtomicInteger();
realIndex.incrementAndGet();

Bloqueo de base de datos pesimista: seleccionar para actualización; bloqueo optimista: verificar versión (como git)

2.3 La sobrecarga de dos tipos de cerraduras

El costo original del bloqueo pesimista es más alto que el del bloqueo optimista, pero las ventajas son de una vez por todas. El costo de las exclusiones mutuas permanece sin cambios.
El bloqueo optimista tiene una pequeña sobrecarga inicial, pero si gira durante mucho tiempo o vuelve a intentarlo, puede consumir más recursos.

2.4 Escenarios de uso

Bloqueo pesimista: es adecuado para múltiples escrituras simultáneas, o cuando la sección crítica mantiene un bloqueo durante mucho tiempo, lo que puede evitar una gran cantidad de giros. Los escenarios típicos son

  1. La sección crítica tiene operaciones IO
  2. El código de la sección crítica es complejo o tiene una gran cantidad de bucles
  3. Feroz competencia en la sección crítica

Bloqueo optimista: adecuado para escenarios donde hay pocas escrituras simultáneas y muchas lecturas, reduciendo bloqueos para mejorar el rendimiento.

3 Bloqueo reentrante

Cuando vuelva a solicitar este candado, no es necesario que lo suelte primero.

Los beneficios de las cerraduras reentrantes sonEvite el punto muerto y mejore la encapsulación. Suponiendo que el hilo T quiere acceder a dos métodos sincronizados A y B al mismo tiempo, si no tiene reentrada, cuando AB necesita el mismo bloqueo, el hilo no puede llamar a B después de llamar a A, lo que conducirá a un punto muerto. El bloqueo reentrante evita el bloqueo y desbloqueo repetidos y es conveniente para el embalaje.

Consulte la introducción de palabras clave para conocer el principio de sincronizado.
Como sugiere el nombre, ReentrantLock es una cerradura reentrante.La naturaleza reentrante de la capa inferior se mantiene mediante el estado de AQS.

Inserte la descripción de la imagen aquí

Bloqueos reentrantes, al agregar y desbloquear, determinará si el hilo actual es el hilo que ya tiene el bloqueo.

4 Cerradura justa y cerradura injusta

La equidad se refiere a la asignación de bloqueos de acuerdo con el orden de las solicitudes de subprocesos, y la injusticia se refiere al hecho de que el orden de las solicitudes no se sigue por completo y, en determinadas circunstancias, se puede saltar a la cola. Injusto, no saltar a ciegas en la fila.

El diseño injusto es para mejorar la eficiencia de la ejecución y evitar el período de inactividad causado por la activación.

Suponiendo que los subprocesos 123 compiten por el mismo bloqueo al mismo tiempo, y el subproceso 1 mantiene el bloqueo, el subproceso 23 está en un estado bloqueado. Una vez que finaliza la ejecución del subproceso 1, el subproceso 23 se activará y hay un período inactivo de activación en este momento. Para un bloqueo injusto, si el subproceso 4 adquiere el bloqueo en este momento, el subproceso 4 puede saltar en la cola y obtener el bloqueo directamente.

// ReentrantLock默认非公平锁
public ReentrantLock() {
    
    
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    
    
    sync = fair ? new FairSync() : new NonfairSync();
}

El método tryLock () del caso especial de ReentrantLock no sigue las reglas de equidad. Incluso si el bloqueo es un bloqueo justo, llamar a tryLock seguirá obteniendo un bloqueo disponible de inmediato, independientemente de que otros subprocesos estén esperando el bloqueo.

4.1 El principio subyacente

Inserte la descripción de la imagen aquí

El bloqueo justo primero determinará si hay subprocesos en la cola.

4.2 Ventajas y desventajas

Los bloqueos injustos son más rápidos y tienen un mayor rendimiento, pero pueden provocar la falta de subprocesos y es posible que los subprocesos no se ejecuten durante mucho tiempo. Los candados justos implementan la igualdad de oportunidades.

5 bloqueos compartidos y bloqueos exclusivos (es decir, bloqueos de lectura y escritura)

Los bloqueos mutex son una estrategia de bloqueo conservadora, que permite que solo un subproceso mantenga el bloqueo a la vez. Evita conflictos de escritura-escritura y conflictos de escritura-lectura, y también evita conflictos de lectura-lectura. En escenarios con muchas operaciones de lectura, se permiten lecturas paralelas, lo que puede mejorar el rendimiento.

Los bloqueos compartidos permiten operaciones de lectura simultáneas por varios subprocesos.

Bloqueo de lectura y escritura, es decir, se puede acceder a un recurso mediante varios subprocesos de lectura o un subproceso de escritura, pero no se pueden realizar los dos al mismo tiempo [ya sea de varias lecturas o una sola escritura]. Bloqueos de lectura-escritura, parece que los bloqueos de lectura y escritura son independientes entre sí, pero en realidad son solo vistas diferentes del objeto de bloqueo de lectura-escritura.

// 读写锁接口,分别返回读/写锁对象
public interface ReadWriteLock {
    
    
    Lock readLock();
    Lock writeLock();
}

El uso de bloqueos de lectura y escritura en escenas de lectura frecuente puede mejorar el rendimiento, mientras que el rendimiento de otras escenas es inferior al de los bloqueos exclusivos porque los bloqueos de lectura y escritura son más complejos.

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; }
  • ReentrantReadWriteLock puede ser reentrante y puede configurarse como un bloqueo justo / injusto.

5.1 Estrategia de bloqueo justo ReentrantReadWriteLock

En cerraduras justas, se da prioridad a la cerradura con mayor tiempo de espera para adquirir la cerradura. Siga primero en entrar, primero en salir (FIFO). Los bloqueos injustos no están seguros sobre la secuencia de subprocesos y se pueden insertar en la cola, pero el subproceso de lectura no puede insertarse en la cola cuando el subproceso en la cabeza de la cola solicita un bloqueo de escritura.

Los bloqueos de lectura y escritura justos e injustos siguen estrictamente las reglas de leer más o escribir una vez.

Pensando en la estrategia de saltarse la cola: si el hilo del lector puede saltarse la cola, aunque se mejora la eficiencia, es fácil provocar la inanición del hilo del escritor. ReentrantReadWriteLock Si el jefe de la cola está esperando el subproceso de escritura, no se le permite leer la cola.

// 公平读写锁策略
static final class FairSync extends Sync {
    
    
    private static final long serialVersionUID = -2274990926593161451L;
    // 写线程是否应该阻塞
    final boolean writerShouldBlock() {
    
    
        return hasQueuedPredecessors();// 队列中是否有排队的线程
    }
    // 读线程是否应该阻塞
    final boolean readerShouldBlock() {
    
    
        return hasQueuedPredecessors(); // 队列中是否有排队的线程
    }
}
// 非公平读写锁策略
static final class NonfairSync extends Sync {
    
    
    private static final long serialVersionUID = -8159625535654395037L;
    final boolean writerShouldBlock() {
    
    
        return false; // 写线程可以插队
    }
    final boolean readerShouldBlock() {
    
    
         // 为了避免无限期的写线程饥饿,
         // 如果队列头是否写线程等待,读线程阻塞
        return apparentlyFirstQueuedIsExclusive();
    }
}

Bloqueo justo: no se permite el salto de cola; bloqueo injusto: el bloqueo de escritura puede saltar a la cola en cualquier momento, y el bloqueo de lectura puede saltar a la cola solo cuando el jefe de la cola no es un hilo esperando el bloqueo de escritura.

5.2 Bloquear degradación

Degradación de bloqueo : un subproceso mantiene un bloqueo de escritura y adquiere un bloqueo de lectura sin liberar el bloqueo de escritura. El bloqueo de escritura se degrada a un bloqueo de lectura.

== ReentrantReadWriteLock, el hilo del escritor se puede degradar a un hilo del lector y el hilo del lector no se puede promover a un hilo del escritor. == Debido a que varios subprocesos de lectura desean actualizar a un bloqueo de escritura, el bloqueo de lectura no se liberará. Cuando el hilo del lector realiza una operación de bloqueo de escritura, se bloqueará y el estado será ESPERA.

Una vez que el bloqueo se haya degradado con éxito, el bloqueo de escritura se liberará directamente y el bloqueo de lectura se mantendrá para mejorar la eficiencia de uso del bloqueo.

Ejemplo de modificación de datos de caché: primero obtenga el bloqueo de lectura, verifique si los datos almacenados en caché no son válidos; luego libere el bloqueo de lectura, obtenga el bloqueo de escritura, actualice el caché, luego obtenga el bloqueo de lectura (bloqueo de degradación) e imprima los datos.

6 cerraduras giratorias y cerraduras de bloqueo

El subproceso en Java corresponde al subproceso del sistema operativo uno a uno. Cuando el bloqueo no se puede adquirir y el subproceso es despertado por el bloqueo, el sistema operativo necesita cambiar el estado de la CPU para completar. Si el tiempo de bloqueo del bloque del código de sincronización es corto, el hilo puede tardar más en suspender y reanudar el hilo que el tiempo de ejecución del código de usuario.

Un bloqueo de giro es cuando un subproceso adquiere un bloqueo y descubre que el bloqueo está retenido por otro subproceso. No se bloquea inmediatamente. En su lugar, intenta adquirir el bloqueo varias veces sin renunciar al derecho de usar la CPU. El bloqueo de bloqueo significa bloquear inmediatamente hasta que se despierte.

El propósito de los bloqueos de giro es reducir el cambio de estado del hilo.

Desventajas de los bloqueos de giro: si el tiempo para sincronizar los recursos es demasiado largo, el tiempo de giro será más largo y el hilo hará mucho trabajo inútil y desperdiciará recursos de la CPU.

Escenarios de aplicación de bloqueos de giro: generalmente se utiliza para servidores de varios núcleos, es más eficiente que bloquear bloqueos cuando el grado de simultaneidad no es alto. Además, es adecuado para situaciones en las que el área crítica es relativamente pequeña.

6.1 Principio y análisis del bloqueo de giro

Las clases atómicas del paquete atómico en JUC son básicamente la implementación de bloqueos de giro. El método getAndIncrement de AtomicInteger que se usa con mucha frecuencia es un bloqueo de giro. El principio subyacente es CAS , que es un bucle de ejecución hasta que la modificación se realiza correctamente.

public final int getAndIncrement() {
    
    
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

public final int getAndAddInt(Object var1, long var2, int var4) {
    
    
    int var5;
    do {
    
    
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

6.2 Implementación simple de un bloqueo de giro

public class SpinLock {
    
    
    private static AtomicReference<Thread> spin = new AtomicReference<>();

    public void lock() {
    
    
        Thread currentThread = Thread.currentThread();
        // 不断CAS尝试设置原子引用
        while (!spin.compareAndSet(null, currentThread)) {
    
    
            System.out.println("获取自旋锁失败再次尝试!");
        }
    }

    public void unlock() {
    
    
        Thread currentThread = Thread.currentThread();
        // 将原子引用设为null
        spin.compareAndSet(currentThread, null);
    }
}

7 Bloqueo de interrupción

El método de bloqueo de sincronizado y bloqueo no se puede interrumpir, mientras que tryLock y lockInterrupt de bloqueo se pueden interrumpir.

Optimización de bloqueo de 8 JVM: mejora el rendimiento

8.1 Aspecto de JVM

  1. El bloqueo de giro y el bloqueo de giro autoadaptativo se pueden usar cuando el contenido del bloqueo es pequeño. Pero el giro no es ciego y, después de muchos intentos, se puede convertir en un bloqueo de bloqueo.
  2. Eliminación de bloqueo: cuando la JVM encuentra que el bloqueo actual está dentro del método y no necesita bloquearse en absoluto, eliminará el bloqueo
  3. Bloqueo grueso: la JVM supervisará dinámicamente el código durante la ejecución. Si los códigos adyacentes utilizan el mismo bloqueo, se realizará el engrosamiento del bloqueo. En este momento, solo se requiere un desbloqueo.

8.2 Puntos a tener en cuenta en el desarrollo de código

  1. Minimice el bloque de código de sincronización tanto como sea posible y coloque la operación atómica en el bloque de código de sincronización
  2. Utilice bloques de código sincronizados según sea necesario, en lugar de métodos de bloqueo
  3. Reducir el número de candados. Por ejemplo, varias operaciones de subprocesos se combinan en una operación grande y se solicita un bloqueo.
  4. Evite la creación artificial de puntos calientes. Por ejemplo, obtenga el tamaño del mapa hash y mantenga el valor de tamaño cada vez que lo agregue / elimine. Al obtener tamaño, solo necesita obtener el valor de la variable de tamaño, no el mapa completo. Esto puede reducir el uso de cerraduras.
  5. Trate de no usar la cerradura dentro de la cerradura, que es fácil de causar un punto muerto.
  6. Elija el tipo de bloqueo correcto o las herramientas adecuadas.

La ubicación del código de caso de este artículo: https://gitee.com/dtyytop/advanced-java

Supongo que te gusta

Origin blog.csdn.net/LIZHONGPING00/article/details/113963573
Recomendado
Clasificación