Clasificación e introducción de cerraduras.

1. Introducción a la cerradura

Lock (Bloqueo) es un mecanismo de control de concurrencia que se utiliza para proteger los recursos compartidos y evitar la inconsistencia de datos o las condiciones de carrera causadas por múltiples subprocesos que acceden o modifican el mismo recurso al mismo tiempo. El bloqueo proporciona un mecanismo de acceso de exclusión mutua para garantizar que solo un subproceso pueda adquirir el bloqueo en un momento determinado y ejecutar el código de la sección crítica. (La esencia del bloqueo es un tipo de recurso, que es un tipo de recurso especialmente utilizado para la sincronización mantenida por el sistema operativo)

Los bloqueos juegan un papel importante en la programación concurrente, se pueden usar para lograr la seguridad de los subprocesos y la coherencia de los datos. Cuando varios subprocesos necesitan acceder a un recurso compartido, compiten por el derecho a utilizar el recurso mediante la adquisición de un bloqueo. Si un subproceso adquiere el bloqueo, otros subprocesos deben esperar hasta que el subproceso que mantiene el bloqueo lo libere. Esto asegura que solo un subproceso pueda acceder al código de la sección crítica a la vez, evitando carreras de datos e inconsistencias.

Los bloqueos comunes incluyen bloqueos incorporados (como la palabra clave sincronizada en Java), bloqueos explícitos (como la clase ReentrantLock en Java), bloqueos de lectura y escritura (como la clase ReentrantReadWriteLock en Java), etc. Además de proporcionar acceso exclusivo mutuo, los bloqueos también pueden admitir la reentrada (un subproceso puede adquirir el mismo bloqueo varias veces), la interrumpibilidad (un subproceso puede interrumpirse mientras espera un bloqueo) y la equidad (oportunidades de adquirir bloqueos en el orden en que esperan los subprocesos).

Mediante el uso razonable de bloqueos, se puede garantizar la corrección y la eficiencia de los programas de subprocesos múltiples y se pueden evitar los problemas causados ​​por el acceso simultáneo a los recursos compartidos. Sin embargo, el uso excesivo de bloqueos también puede causar problemas como interbloqueos o cuellos de botella en el rendimiento.Por lo tanto, al diseñar programas concurrentes, es necesario seleccionar un mecanismo de bloqueo apropiado según la situación específica.

1.1 Bloqueos en Java

El mecanismo de bloqueo en Java se implementa principalmente en base a Monitor Lock. Un monitor es un mecanismo de sincronización en Java que se utiliza para implementar el acceso de exclusión mutua de subprocesos y coordinar la comunicación entre subprocesos.

En Java, cada objeto tiene un monitor incorporado (también conocido como bloqueo), que se puede adquirir y liberar a través de la palabra clave sincronizada. Cuando un hilo entra en un bloque de código sincronizado o método, intentará obtener el monitor del objeto correspondiente, si el monitor ya está ocupado por otros hilos, el hilo entrará en estado bloqueado hasta que se pueda obtener el monitor para continuar con la ejecución.

Basado en el mecanismo de bloqueo del monitor, Java brinda soporte para el acceso mutuamente excluyente para garantizar que solo un subproceso pueda ejecutar el código de la sección crítica al mismo tiempo, evitando así la competencia de datos y los problemas de inconsistencia. Al mismo tiempo, el bloqueo del monitor de Java también proporciona funciones como reingreso (el mismo subproceso puede adquirir el mismo bloqueo varias veces), bloqueo interno y mecanismos de espera/notificación.

Además de la palabra clave sincronizada basada en bloqueos de monitor, Java también proporciona implementaciones de bloqueo más flexibles, como la clase ReentrantLock, que es una implementación de bloqueos explícitos. En comparación con los bloqueos de monitor incorporados, los bloqueos explícitos pueden proporcionar más funciones, como bloqueos temporizados e interrumpibles.

En resumen, el mecanismo de bloqueo en Java se implementa principalmente en función de los bloqueos del monitor. Mediante el uso de la palabra clave sincronizada o bloqueos explícitos, las operaciones de sincronización y acceso simultáneo de subprocesos se pueden administrar de manera efectiva.

1.2 Artículos relacionados:

sincronizado y monitorizado lock_Do test meow sauce's blog-CSDN blog

Bloqueo de monitor (Bloqueo de monitor)

2. Clasificación de las cerraduras

Las cerraduras se pueden dividir según las siguientes dimensiones:

1. Modo de propiedad: El modo de propiedad de los candados se refiere a la adquisición y liberación de candados, los cuales se dividen principalmente en dos tipos:

  • Bloqueo exclusivo: solo se permite que un subproceso adquiera el bloqueo y otros subprocesos deben esperar. Los bloqueos exclusivos comunes incluyen bloqueos integrados (sincronizados) y bloqueos explícitos (como ReentrantLock).
  • Bloqueo compartido: permite que varios subprocesos adquieran bloqueos al mismo tiempo y lean recursos compartidos al mismo tiempo, pero evita que otros subprocesos escriban. Un bloqueo compartido común es un bloqueo de lectura y escritura (ReentrantReadWriteLock).

2. Estrategia de concurrencia: La estrategia de concurrencia del bloqueo se refiere a la estrategia de acceso a recursos del bloqueo en un entorno concurrente. Las estrategias de concurrencia comunes son:

  • Bloqueo optimista: el control de concurrencia se realiza mediante la identificación del estado del recurso, generalmente mediante un número de versión (Versioning) o una marca de tiempo (Timestamp).
  • Bloqueo pesimista: de forma predeterminada, los bloqueos exclusivos se utilizan para proteger los recursos compartidos, y los bloqueos se adquieren antes de acceder a los recursos, y se bloquea el acceso de otros subprocesos.

3. Escenarios de aplicación: Los escenarios de aplicación de la cerradura se pueden seleccionar de acuerdo con las necesidades específicas y las condiciones de uso. Los escenarios de aplicación comunes incluyen:

  • Bloqueo incorporado (sincronizado): el código de sección crítica que se usa para proteger el objeto se usa para lograr la seguridad de subprocesos.
  • Bloqueos explícitos (como ReentrantLock): proporciona operaciones de bloqueo más flexibles, como reentrante, interrumpible, equidad y otras características.
  • Bloqueo de lectura y escritura (ReentrantReadWriteLock): es adecuado para la situación de más lecturas y menos escrituras, y proporciona control de simultaneidad de lectura compartida y escritura exclusiva.

En resumen, los bloqueos se pueden clasificar según sus métodos de propiedad, estrategias de concurrencia y escenarios de aplicación. La clasificación específica también variará según el lenguaje de programación, el marco y el entorno de uso. Los diferentes tipos de bloqueos tienen sus propias ventajas y desventajas.La elección de un mecanismo de bloqueo adecuado puede mejorar el rendimiento de la simultaneidad y la coherencia de los datos.

3. Bloqueo optimista y bloqueo pesimista

3.1 Cerradura optimista

Optimistic Locking es un mecanismo de control de concurrencia que se utiliza para tratar problemas de consistencia de datos durante operaciones concurrentes. En comparación con el bloqueo pesimista (Bloqueo pesimista), que necesita mantener el bloqueo durante toda la operación, el bloqueo optimista adopta una estrategia más relajada, asumiendo que no se producirán conflictos durante la operación.

La idea básica del bloqueo optimista:

Cada vez que se actualicen los datos, primero lea el número de versión actual (o un valor que indique el estado de los datos, como una marca de tiempo) y luego verifique si el número de versión de los datos ha cambiado antes de realizar la operación de modificación. Si el número de versión de los datos no ha cambiado, significa que ningún otro subproceso ha modificado los datos durante la operación, entonces el bloqueo optimista cree que la operación puede tener éxito, de lo contrario, significa que otros subprocesos han modificado los datos. En este momento, puede haber conflictos en la operación y deben tratarse en consecuencia.

En la implementación del bloqueo optimista, generalmente se usa un campo de número de versión o un campo de marca de tiempo para identificar la versión de los datos. Cuando el subproceso lea los datos, registrará el número de versión actual al mismo tiempo. Cuando un subproceso desea actualizar datos, verificará nuevamente si el número de versión actual es consistente con el número de versión registrado inicialmente antes de realizar la operación de actualización. Si es consistente, significa que los datos no han sido modificados por otros subprocesos, se puede realizar la operación de actualización y se puede aumentar el número de versión. Si es inconsistente, significa que los datos han sido modificados por otros subprocesos. En este momento, puede optar por abandonar la operación, volver a intentar la operación o manejar el conflicto a través de otra lógica.

Ventajas del bloqueo optimista:

Pero en la mayoría de los casos, no es necesario bloquear, lo que evita la competencia y la espera entre subprocesos. Esto mejora el rendimiento y el rendimiento de la simultaneidad.

pregunta:

El bloqueo optimista también tiene algunos problemas. Por ejemplo, pueden producirse errores de actualización causados ​​por conflictos y se requiere un mecanismo de reintento adecuado. Para escenarios complejos que requieren varias operaciones, el bloqueo optimista puede ser más complicado de implementar.

En aplicaciones prácticas, el bloqueo optimista generalmente se usa en combinación con mecanismos como números de versión, marcas de tiempo o valores hash para proporcionar un control de concurrencia simple y eficiente. En el campo de las bases de datos, el bloqueo optimista se usa a menudo para resolver problemas de actualización concurrente, y existen aplicaciones similares en sistemas distribuidos.

3.1.1 Aplicación de bloqueo optimista en base de datos

En las bases de datos, el bloqueo optimista se usa a menudo para manejar escenarios de actualización simultánea para garantizar la coherencia y la integridad de los datos. A continuación, se presentan algunos métodos comunes de aplicación de bloqueo optimista en la base de datos:

  1. Control de versiones: agregue un nuevo campo de número de versión en la tabla de datos, y cada operación de actualización actualizará el valor de este campo. Al realizar una operación de actualización, primero lea el número de versión de los datos actuales y luego verifique si el número de versión es consistente antes de realizar la operación de actualización. Si es consistente, significa que ningún otro subproceso ha modificado los datos, y puede realizar una operación de actualización y aumentar el número de versión; si es incoherente, significa que otros subprocesos han modificado los datos. En este momento, puede optar por abandonar la operación, volver a intentar la operación o manejar los conflictos.
  2. Marca de tiempo: similar al número de versión, la marca de tiempo se utiliza para registrar la hora de modificación de los datos. Antes de realizar la operación de actualización, se juzga si los datos han sido modificados por otros subprocesos comparando la marca de tiempo de los datos actuales con la marca de tiempo registrada en el momento de la lectura inicial.
  3. Valor hash: genera un valor hash a partir del contenido de los datos y almacena el valor hash en la tabla de datos. Cuando se realiza una operación de actualización, el hash de los datos se vuelve a calcular y se compara con el hash guardado cuando se leyó originalmente. Si los valores de hash son iguales, significa que los datos no se han modificado y se pueden actualizar; si los valores de hash son diferentes, significa que otros hilos han modificado los datos.
  4. Columna de verificación: Agregue una columna de verificación adicional en la tabla de datos para registrar alguna información de estado de los datos. Antes de realizar la operación de actualización, se juzga si la operación de actualización se puede realizar de acuerdo con el estado de la columna de verificación.

La aplicación del bloqueo optimista generalmente requiere cierto soporte de programación, como el uso de cláusulas WHERE en las declaraciones SQL para juzgar las condiciones, o el uso de funciones o declaraciones relacionadas con el bloqueo optimista para tratar los conflictos de concurrencia. En aplicaciones prácticas, la implementación específica del bloqueo optimista variará según el tipo, las características y los requisitos comerciales de la base de datos.

3.1.2 Aplicación de bloqueo optimista en java

En Java, la aplicación de bloqueo optimista generalmente implica actualizaciones simultáneas de datos compartidos en un entorno de subprocesos múltiples. Aquí hay algunas implementaciones comunes de bloqueo optimista y sus ejemplos:

1. Usar el número de versión (Versioning)

public class OptimisticLockExample {
    private int value;
    private int version;

    public synchronized void updateValue(int newValue) {
        // 保存原始版本号
        int oldVersion = version;
        
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 检查版本号是否发生变化
        if (oldVersion == version) {
            value = newValue;
            version++; // 修改版本号
            System.out.println("Update succeeded!");
        } else {
            System.out.println("Update failed due to concurrent modification!");
        }
    }
}

2. Usa la clase Atómica:

import java.util.concurrent.atomic.AtomicReference;

public class OptimisticLockExample {
    private AtomicReference<Integer> value = new AtomicReference<>();
    
    public void updateValue(int newValue) {
        // 获取当前值和版本号
        Integer oldValue = value.get();
        Integer oldVersion = oldValue != null ? oldValue.hashCode() : null;

        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 检查值和版本号是否发生变化
        if (value.compareAndSet(oldValue, newValue)) {
            System.out.println("Update succeeded!");
        } else {
            System.out.println("Update failed due to concurrent modification!");
        }
    }
}

La clase AtomicReference se usa aquí para garantizar operaciones atómicas, y el método hashCode() se usa para generar el número de versión. El método compareAndSet() compara si el valor antiguo es igual al valor actual y actualiza si son iguales.

La implementación específica del bloqueo optimista también puede usar una estructura de datos sin bloqueo (como el algoritmo CAS), el complemento de bloqueo optimista de la base de datos, la anotación de bloqueo optimista, etc. Todos estos métodos tienen como objetivo evitar conflictos de datos causados ​​por operaciones concurrentes y garantizar la coherencia de los datos a través de ciertos mecanismos o algoritmos. En aplicaciones prácticas, es muy importante elegir una implementación de bloqueo optimista que se adapte al escenario.

3.1.2.1 Extensiones:

Atomicidad:

La atomicidad significa que una operación se ejecuta completamente con éxito o no se ejecuta en absoluto, y no habrá un estado intermedio o una ejecución parcial. La atomicidad garantiza la coherencia y la fiabilidad de las operaciones en un entorno de subprocesos múltiples.

En la programación concurrente, la atomicidad es muy importante para las operaciones de lectura y escritura en datos compartidos. Cuando múltiples subprocesos acceden y modifican los mismos datos al mismo tiempo, si no hay garantía de atomicidad, puede generar inconsistencia de datos, condiciones de carrera y otros problemas.

Para asegurar la atomicidad, Java proporciona varios mecanismos y clases, tales como:

  • La palabra clave sincronizada: use la palabra clave sincronizada para marcar un método o bloque de código como sincronizado, lo que permite que solo un subproceso ingrese al área de sincronización para evitar el acceso simultáneo.
  • Interfaz de bloqueo y su clase de implementación: la interfaz de bloqueo proporciona un mecanismo de bloqueo más flexible que el sincronizado, como ReentrantLock, que proporciona características como la reentrada y la equidad.
  • Clase atómica: la clase atómica del paquete java.util.concurrent.atomic proporciona algunas clases de operaciones atómicas, como AtomicInteger, AtomicLong, etc., que garantizan la atomicidad de operaciones específicas.
  • Contenedores atómicos: las clases de contenedores como ConcurrentHashMap y ConcurrentLinkedQueue en Java proporcionan operaciones atómicas y evitan las inconsistencias de datos causadas por operaciones concurrentes.

En resumen, garantizar la atomicidad de las operaciones puede eliminar el problema de la competencia de datos en la programación concurrente y garantizar la coherencia de los datos en un entorno de subprocesos múltiples. El uso adecuado de los mecanismos de sincronización y las operaciones atómicas puede mejorar eficazmente el rendimiento y la corrección concurrentes de los programas.

3.2 Bloqueo pesimista

3.2.1 ¿Qué es un bloqueo pesimista?

El bloqueo pesimista es un mecanismo de control de concurrencia basado en una suposición pesimista de que es probable que se produzcan conflictos durante el acceso simultáneo a los recursos compartidos. Por lo tanto, cuando se usa el bloqueo pesimista, el programa asume que otros subprocesos modificarán los recursos compartidos y toma las medidas adecuadas para evitar conflictos e inconsistencias de datos.

La característica principal del bloqueo pesimista es que antes de acceder a los recursos compartidos, se adquirirá el bloqueo para garantizar que el subproceso actual pueda monopolizar el recurso, y el bloqueo se liberará una vez que se complete la operación. La implementación de bloqueos pesimistas incluye el uso de mutexes, palabras clave sincronizadas, bloqueos de bases de datos, etc.

Cuando un subproceso adquiere el bloqueo pesimista, otros subprocesos deben esperar hasta que se libere el bloqueo, lo que garantiza que solo un subproceso pueda modificar el recurso compartido al mismo tiempo, evitando así los problemas de competencia de datos y conflictos de concurrencia. Sin embargo, el bloqueo pesimista puede provocar una degradación del rendimiento en entornos de alta simultaneidad porque limita la cantidad de subprocesos que pueden acceder simultáneamente a los recursos compartidos.

El bloqueo pesimista es adecuado para escenarios en los que los recursos compartidos se modifican con frecuencia y proporciona un método de control de concurrencia conservador para garantizar la seguridad y la coherencia de los datos. Sin embargo, con la aparición de mecanismos de control de concurrencia más eficientes, como el bloqueo optimista, es posible que el bloqueo pesimista ya no sea la mejor opción en algunos escenarios.

3.2.2 Aplicación del bloqueo pesimista en java

En Java, la aplicación del bloqueo pesimista generalmente involucra la protección de secciones críticas de los datos compartidos. Antes de acceder a los datos compartidos, el bloqueo pesimista asume que otros subprocesos modificarán los datos, por lo que primero adquiere el bloqueo, luego realiza la operación y libera el bloqueo una vez que se completa la operación. Aquí hay algunas implementaciones de bloqueo pesimista comunes y sus ejemplos:

Utilice la palabra clave sincronizada:

public class PessimisticLockExample {
    private int value;
  
    public synchronized void updateValue(int newValue) {
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        value = newValue;
        System.out.println("Update succeeded!");
    }
}

En este ejemplo, al usar la palabra clave sincronizada para modificar el método, se garantiza que cuando un subproceso ejecuta el método updateValue(), otros subprocesos no pueden ingresar al método al mismo tiempo, lo que garantiza un acceso mutuamente excluyente a los datos compartidos.

Use la interfaz Lock y su clase de implementación:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PessimisticLockExample {
    private int value;
    private Lock lock = new ReentrantLock();
  
    public void updateValue(int newValue) {
        lock.lock(); // 获取锁

        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        value = newValue;
        System.out.println("Update succeeded!");

        lock.unlock(); // 释放锁
    }
}

En este ejemplo, se crea un objeto de bloqueo mediante la clase de implementación ReentrantLock y se llama a los métodos lock() y unlock() antes y después de la sección crítica del código que debe protegerse para adquirir y liberar el bloqueo.

La implementación específica del bloqueo pesimista también puede utilizar el mecanismo de bloqueo de la base de datos, el bloqueo distribuido, etc. Todos estos métodos están diseñados para garantizar la exclusividad de los recursos compartidos mediante el bloqueo, evitando que otros subprocesos accedan y modifiquen los datos al mismo tiempo. Sin embargo, el bloqueo pesimista conducirá a una mayor competencia de subprocesos y a una reducción del rendimiento de la concurrencia. Por lo tanto, en las aplicaciones prácticas, es necesario sopesar los escenarios y los costos del uso del bloqueo pesimista.

Supongo que te gusta

Origin blog.csdn.net/qq_39208536/article/details/131814501
Recomendado
Clasificación