Programación concurrente de Java (3) sincronización de subprocesos [sincronizada | volátil]

concepto

Cuando se utilizan varios subprocesos para acceder a los mismos datos, se producirán datos inexactos y conflictos entre sí, lo que es muy propenso a problemas de seguridad de los subprocesos. Por ejemplo, varios subprocesos operan con los mismos datos y tienen la intención de modificar el inventario de productos. por lo que esto puede generar problemas de inconsistencia en los datos.
Por lo tanto, garantizamos la seguridad de los subprocesos a través del mecanismo de sincronización de subprocesos y agregamos bloqueos de sincronización para evitar ser llamados por otros subprocesos antes de que el subproceso complete la operación, garantizando así la unicidad y precisión de la variable. La esencia de la sincronización de subprocesos es "poner en cola": varios subprocesos deben ponerse en cola y luego operar recursos compartidos uno por uno en lugar de al mismo tiempo, garantizando así la seguridad de los subprocesos (es decir, garantizando atomicidad , visibilidad y orden ) .

Cerrar

Descripción general

En el entorno de subprocesos múltiples de Java, utilizamos bloqueos para garantizar la corrección y la seguridad de los subprocesos de los recursos compartidos . Es decir, bloquear el recurso antes de que un subproceso opere un recurso compartido para garantizar que ningún otro subproceso acceda al recurso durante la operación. La operación se completa. Luego libere el bloqueo del recurso compartido para que otros subprocesos puedan acceder.

  • El bloqueo en Java es un mecanismo de sincronización que se utiliza para controlar el acceso de múltiples subprocesos a recursos compartidos.
  • Los bloqueos evitan que varios subprocesos escriban en el mismo recurso compartido al mismo tiempo, evitando así inconsistencias y errores de datos.
  • Un bloqueo es una herramienta de exclusión mutua que garantiza que solo un hilo pueda acceder a recursos compartidos al mismo tiempo.
  • Los bloqueos en Java se pueden utilizar para proteger recursos compartidos en diversas granularidades, como bloques de código, objetos, métodos, clases, etc.
  • Los bloqueos permiten que varios subprocesos accedan a recursos compartidos en un orden específico, evitando así problemas de concurrencia como interbloqueos y condiciones de carrera.
  • Los bloqueos de uso común en Java incluyen la palabra clave sincronizada, ReentrantLock, ReadWriteLock, Semaphore, etc. Estos bloqueos proporcionan diferentes funciones y características de rendimiento.

Clasificación

Desde la perspectiva de la concurrencia, las estrategias de seguridad de subprocesos se pueden dividir en tres tipos (nuestro desarrollo diario involucra principalmente los dos primeros)

  • El primero es el bloqueo pesimista, cuyo núcleo es la sincronización de exclusión mutua ( sincronizado, sistema de bloqueo )
  • El segundo es el bloqueo optimista, el núcleo es la sincronización sin bloqueo y las operaciones atómicas se realizan a través de CAS, es decir, sin bloqueo ( la capa inferior es volátil+CAS )
  • La tercera es una solución sin sincronización, que incluye código reentrante y almacenamiento local de subprocesos.

Introducción a las cerraduras de uso común.

  • Bloqueo reentrante : un bloqueo reentrante es un bloqueo que se puede adquirir varias veces, lo que permite que un subproceso adquiera el bloqueo nuevamente mientras lo adquiere. Proporciona el mismo control de acceso mutuamente excluyente que la palabra clave sincronizada, pero con mayor flexibilidad y funcionalidad mejorada.
  • Bloqueo de lectura y escritura (ReadWriteLock): el bloqueo de lectura y escritura es un tipo especial de bloqueo que permite que varios subprocesos lean recursos compartidos al mismo tiempo, pero solo permite que un subproceso escriba en recursos compartidos. En el caso de más lectura y menos escritura, los bloqueos de lectura y escritura pueden mejorar el rendimiento de concurrencia del programa.
  • Bloqueo justo (FairLock): el bloqueo justo garantiza que el orden en que los subprocesos adquieren bloqueos sea el mismo que el orden en que los subprocesos solicitan bloqueos. Si hay una cola de espera, el hilo que espere más tiempo adquirirá el bloqueo.
  • Mutex: Mutex es el bloqueo más simple: bloquea un recurso compartido para garantizar que solo un hilo pueda acceder al recurso al mismo tiempo.
  • Semáforo : un semáforo es una herramienta de sincronización que se puede utilizar para controlar el acceso a recursos compartidos. Permite que varios subprocesos accedan a un recurso compartido simultáneamente, pero limita la cantidad de subprocesos que pueden acceder al recurso simultáneamente.
  • Bloqueo sesgado: el bloqueo sesgado es un método de optimización que puede reducir la competencia de bloqueos en un entorno de subprocesos múltiples. Su idea básica es desviar el candado hacia el primer subproceso para adquirir el candado sin competencia, evitando así que otros subprocesos compitan por el candado.

Escenarios de aplicación

Los bloqueos multiproceso son un mecanismo de sincronización que se utiliza para proteger los recursos compartidos en la programación multiproceso. Los siguientes son escenarios adecuados para el uso de bloqueos multiproceso:

  • Acceso a la base de datos: el acceso de varios subprocesos a la base de datos al mismo tiempo puede causar problemas de coherencia de los datos. El uso de bloqueos puede garantizar la integridad y corrección de los datos.
  • Lectura y escritura de archivos: varios subprocesos que leen y escriben el mismo archivo al mismo tiempo pueden causar daños en el archivo o pérdida de datos. El uso de bloqueos puede garantizar la integridad y corrección del archivo.
  • Memoria compartida: cuando varios subprocesos acceden a la misma memoria compartida, el uso de bloqueos puede garantizar que cada subproceso pueda leer o escribir datos correctamente en la memoria compartida.
  • Operación de la cola: varios subprocesos que operan en la cola al mismo tiempo pueden causar confusión o pérdida de datos. El uso de bloqueos puede garantizar la secuencia de las operaciones de la cola y la exactitud de los datos.
  • Comunicación de red: cuando varios subprocesos realizan comunicación de red al mismo tiempo, el uso de bloqueos puede garantizar la integridad y corrección de la transmisión de datos.

Nota: El uso excesivo de bloqueos puede reducir el rendimiento del programa. Al utilizar cerraduras, se debe prestar atención a sopesar la granularidad y los requisitos de rendimiento de la cerradura.

Mecanismo de sincronización

sincronizado

Descripción general

  • sincronizado es una palabra clave en Java y es un bloqueo pesimista sincrónico
  • Los objetos comúnmente utilizados para la modificación incluyen los siguientes:
    • Modificar un bloque de código. El bloque de código modificado se llama bloque de declaración de sincronización. Su alcance es el código encerrado entre llaves {}, y el objeto sobre el que actúa es el objeto que llama a este bloque de código.
    • Modificar un método. El método modificado se llama método de sincronización. Su alcance es el método completo y el objeto sobre el que actúa es el objeto que llama a este método.
    • Modifique un método estático, su alcance es todo el método estático y su objetivo son todos los objetos de esta clase
    • Al modificar una clase, su alcance es la parte entre paréntesis después de sincronizar, y su alcance son todos los objetos de esta clase.
  • Realice el problema productor-consumidor mediante sincronizado + esperar + notificar (habrá ejemplos relevantes en el capítulo de práctica de concurrencia más adelante)

Principio de implementación

sincronizado se implementa en función de los bits de bandera en el encabezado del objeto Java. Hay dos bits de bandera en el encabezado del objeto Java que se utilizan para almacenar información de bloqueo sincronizado:

  • Uno es una bandera que indica si el objeto actual está bloqueado.
  • Uno es el identificador del hilo que sostiene el candado.
Descripción del proceso de ejecución
  • Cuando un hilo intenta obtener un recurso protegido por un bloqueo sincronizado ( es decir, cuando se ejecuta la instrucción monitorenter ), la JVM primero verificará el indicador de bloqueo del objeto. Si el indicador de bloqueo es 0, significa que el objeto está no está bloqueado y la JVM lo hará. El bit de bandera se establece en 1 y el identificador del subproceso que mantiene el bloqueo se establece en el identificador del subproceso actual. Si el indicador de bloqueo es 1, significa que el objeto ha sido bloqueado por otros subprocesos y el subproceso actual entrará en el estado de bloqueo y esperará a que otros subprocesos liberen el bloqueo;
  • Cuando un subproceso libera un recurso protegido por un bloqueo sincronizado ( es decir, cuando se ejecuta la instrucción monitorexit ), la JVM establecerá el indicador de bloqueo en 0 y borrará la identificación del subproceso para liberar el objeto. Al mismo tiempo, la JVM despertar otros hilos esperando el bloqueo del objeto para que puedan continuar compitiendo por los bloqueos
comando de monitor

Después de descompilar el archivo de código de bytes, encontramos que la capa inferior sincronizada usa la instrucción del monitor para lograr la sincronización ; la instrucción del monitor incluye monitorenter y monitorexit, que pueden entenderse como el código que comienza a sincronizar/iniciar el bloqueo y finalizar la sincronización/finalizar el bloqueo;

  • La instrucción monitorenter se utiliza para bloquear: después de ingresar el código de sincronización, es necesario obtener los datos más recientes antes y después de cada operación y, después de la ejecución, se vuelven a escribir en la memoria principal a tiempo.
  • La instrucción monitorexit libera el bloqueo: establezca el indicador de bloqueo del objeto en 0, borre la ID del hilo y active otros hilos que esperan el bloqueo del objeto para que puedan continuar compitiendo por el bloqueo.
Obtenga el objeto singleton TestMultiThread (usando DCL)
javap -v .\TestMultiThread.class ( descompila la clase compilada )

Nota : ¿ Por qué el comando monitorexit aparece dos veces?

  • La primera instrucción de monitorexit es una señal de que el bloque de código sincronizado libera el bloqueo normalmente.
  • Si se produce una excepción o un error en el bloque de código sincronizado, se llamará a la segunda instrucción monitorexit para garantizar que se libere el bloqueo.

Optimización de bloqueo

Descripción general

Una mejora importante después de actualizar de JDK5 a JDK6, el equipo de desarrollo de la máquina virtual HotSpot gastó muchos recursos para implementar varias tecnologías de optimización de bloqueo, como el giro adaptativo (Adaptive Spinning), la eliminación de bloqueo (Lock Elimination) y el engrosamiento de bloqueo. Engrosamiento), bloqueo sesgado (bloqueo sesgado), bloqueo ligero (bloqueo ligero), etc. Todas estas tecnologías están diseñadas para compartir datos de manera más eficiente entre subprocesos y resolver problemas de competencia, mejorando así la eficiencia de ejecución del programa.

rugosidad de la cerradura

Supongamos que una serie de operaciones consecutivas bloquearán y desbloquearán repetidamente el mismo objeto, e incluso la operación de bloqueo ocurre en el cuerpo del bucle. Incluso si no hay competencia de subprocesos, las operaciones frecuentes de sincronización de exclusión mutua causarán una pérdida innecesaria de rendimiento. Si la JVM detecta que hay una serie de operaciones fragmentadas que bloquean el mismo objeto, ampliará el alcance de la sincronización del bloqueo (es decir, el engrosamiento del bloqueo) al exterior de toda la secuencia de operaciones.

eliminación de bloqueo

La eliminación de bloqueo significa eliminar operaciones de bloqueo innecesarias. La eliminación de bloqueos se produce cuando la máquina virtual Java escanea el contexto en ejecución durante la compilación JIT para eliminar bloqueos que probablemente no compitan por los recursos compartidos. Mediante la eliminación de bloqueos, se puede ahorrar tiempo de bloqueo de solicitudes sin sentido.

  • Por ejemplo, el método StringBuffer.append() usa la palabra clave sincronizada para la protección de seguridad del subproceso. Sin embargo, si el objeto StringBuffer solo se usa como una variable local dentro del subproceso, no ocurrirá la llamada situación insegura del subproceso. En este momento , Java se inicia en modo Servidor y la configuración de análisis de escape se ha activado, luego el compilador optimizará este código y eliminará el bloqueo.

Cerraduras sesgadas y cerraduras ligeras.

  • Bloqueo sesgado: Elimina toda la sincronización sin competencia y sin operación CAS. En pocas palabras, hay un campo ThreadId en el encabezado del objeto de bloqueo. Si este campo está vacío, cuando se adquiere el bloqueo por primera vez, su propio ThreadId se escribirá en el campo ThreadId del bloqueo y el encabezado del bloqueo estará sesgado. El estado del bloqueo se establece en 1 (el bit de bandera de arriba). Al adquirir el bloqueo, se verifica directamente si el ThreadId es consistente con su propio ID de hilo. Si es consistente, el actual Se considera que el hilo ha adquirido el bloqueo. Pero cuando el bloqueo tiene una relación competitiva, es necesario liberar el bloqueo sesgado para que el bloqueo entre en un estado competitivo ( actualmente el bloqueo sesgado JDK está activado de forma predeterminada ).
  • Bloqueo ligero: el uso del encabezado del objeto de operación CAS sin contención reemplazará la ID del hilo y el puntero al registro de bloqueo. Si tiene éxito, se obtendrá el candado, si falla, girará y esperará a obtener el candado. Mecanismo: cada bloqueo está asociado con un contador de solicitudes y un hilo que lo posee. Cuando el contador de solicitudes es 0, el bloqueo se puede considerar no desbloqueado. Cuando un hilo solicita un bloqueo no retenido, la JVM registra el propietario del bloqueo. Y agrega 1 al recuento de solicitudes de bloqueo. Si el mismo hilo solicita el bloqueo nuevamente, el contador de solicitudes aumentará. Cuando el hilo sale del bloque sincronizado, el contador se reduce en 1. Cuando el contador llega a 0, el bloqueo se libera (esto garantiza que el bloqueo es reentrante y no se producirá ningún punto muerto)

actualización de bloqueo

Descripción general

Después de JDK1.6, la sincronización se optimizó para el rendimiento y se introdujeron bloqueos livianos y bloqueos sesgados para reducir el consumo de rendimiento, por lo que no se considera completamente un bloqueo pesado. El proceso de actualización del bloqueo se completa automáticamente y se basará en JVMla sincronización JVM. Situaciones de competencia para 自动seleccionar el nivel de bloqueo apropiado para proporcionar un mejor rendimiento y eficiencia. Hay cuatro estados de bloqueo en JDK1.6, a saber, sin bloqueo, bloqueo ligero (giro), bloqueo sesgado y peso pesado . El proceso de actualización del bloqueo va desde bloqueo sesgado -> bloqueo ligero -> bloqueo pesado, y el bloqueo no se puede degradar después de la actualización.

Proceso de actualización de bloqueo

Cuando el primer subproceso accede al bloque sincronizado, la JVM registra la ID del subproceso en el encabezado del objeto y establece el estado de marca del objeto en bloqueo sesgado (el bloqueo sesgado ocurre en un escenario donde solo un subproceso compite por el bloqueo al mismo tiempo ) . Si varios subprocesos compiten por el candado al mismo tiempo, el candado sesgado se actualizará a un candado liviano. Si la operación de giro CAS del hilo alcanza un cierto número de veces y aún no compite por el candado, el candado liviano se actualizará a un candado pesado.

  • Estado inicial: el objeto no tiene marca de bloqueo, es decir, está en un estado libre de bloqueo.
  • Aplicación de bloqueo sesgado: cuando el primer subproceso accede al bloque sincronizado, la JVM registra la ID del subproceso en el encabezado del objeto y establece el estado de marca del objeto en bloqueo sesgado.
  • Revocación del bloqueo sesgado: cuando otros subprocesos intentan adquirir el bloqueo y descubren que el bloqueo sesgado del objeto está ocupado, el bloqueo sesgado se revocará y se actualizará a un bloqueo liviano.
  • Bloqueo ligero: el bloqueo ligero significa que cuando varios subprocesos compiten ligeramente por un bloque sincronizado, la JVM almacenará el registro de bloqueo del objeto en el marco de la pila del subproceso en lugar de en el encabezado del objeto. Antes de ingresar al bloque sincronizado, el hilo intenta adquirir el bloqueo mediante la operación de giro CAS (Comparar e intercambiar). Si la operación de giro de CAS es exitosa, significa que el bloqueo se adquirió con éxito y se ingresa al bloque de sincronización. El bloqueo actual todavía está en el estado de bloqueo liviano; si el CAS falla, significa que hay competencia y se actualiza a una cerradura pesada.
  • El bloqueo pesado significa que cuando varios subprocesos compiten ferozmente por un bloque sincronizado, la JVM actualizará el bloqueo del objeto a un bloqueo pesado y utilizará el mutex proporcionado por el sistema operativo para implementar el mecanismo de bloqueo. Las cerraduras pesadas implican operaciones de bloqueo y activación de subprocesos, que son costosas.

volátil

Descripción general

  • En comparación con sincronizado (bloqueo pesado), volitate es una palabra clave de mecanismo de sincronización liviano proporcionada por JVM porque no provoca cambio ni programación de contexto de subprocesos.
  • La seguridad del hilo no se puede garantizar porque no tiene "exclusividad mutua" y no puede garantizar la atomicidad de las variables.

Características

  • Visibilidad garantizada ( principio de coherencia de la caché )
    • Al escribir una variable volátil, JMM forzará que la variable en la memoria local del hilo se vacíe a la memoria principal.
    • Esta operación de escritura hará que el caché de variables volátiles en otros subprocesos no sea válido.
  • Garantizar el orden
    • El orden se logra prohibiendo el reordenamiento de instrucciones a través de instrucciones relacionadas con la barrera de la memoria (instrucciones de bloqueo) ( el reordenamiento se refiere a un método utilizado por compiladores y procesadores para ordenar secuencias de instrucciones con el fin de optimizar el rendimiento del programa. Los resultados se pueden garantizar en un solo hilo. Corrección, pero el resultado puede no ser correcto en un entorno multiproceso)
    • Función de barrera de la memoria
      • Asegura que cuando se reordenan las instrucciones, las siguientes instrucciones no se ordenarán antes de la barrera de la memoria, ni las instrucciones anteriores se ordenarán detrás de la barrera de la memoria; es decir, cuando se ejecuta la instrucción de la barrera de la memoria, las instrucciones delante de No se arreglará. Todas las operaciones han sido completadas.
      • Forzará que las operaciones de modificación en el caché (memoria de trabajo privada en el hilo) se escriban inmediatamente en la memoria principal (memoria del montón).
      • Si se trata de una operación de escritura, hará que la línea de caché correspondiente en otros subprocesos no sea válida
  • La atomicidad no está garantizada.
    • Volatile no es adecuado para operaciones compuestas (como volatile++) porque no se puede garantizar la atomicidad.

Escenarios de uso comunes

  • Marca de cantidad de estado , como: volátil bool flag = false; para operaciones de lectura y escritura en variables, marcarlas como volátiles puede garantizar que la modificación de las variables sea inmediatamente visible para el subproceso, lo que tiene una cierta mejora de eficiencia en comparación con sincronizado y Lock. implementaciones.
  • Ejemplo de cómo garantizar la seguridad de subprocesos en modo singleton mediante el uso típico de bloqueo de doble verificación (DCL)
//懒汉单例模式
class Singleton{
    private volatile static Singleton instance = null;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) { //将同步的粒度降到方法内部,提高了程序的性能
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_34020761/article/details/132211509
Recomendado
Clasificación