JUC: bloqueos pesimistas de subprocesos múltiples, bloqueos optimistas, bloqueos de lectura y escritura (bloqueos compartidos, bloqueos exclusivos), bloqueos justos e injustos, bloqueos reentrantes, bloqueos giratorios, puntos muertos (10)

Existen principalmente los siguientes bloqueos en Java

1. Bloqueo pesimista, bloqueo optimista

Bloqueo pesimista : cuando el subproceso actual opera datos, siempre piensa que otros subprocesos modificarán los datos, por lo que se bloqueará cada vez que opere datos, y otros subprocesos se bloquearán cuando operen datos, como sincronizados;

Bloqueo optimista : cada vez que el subproceso actual opera datos, piensa que otros no los modificarán. Al actualizar, juzgará si otros actualizarán los datos. Se juzga por la versión. Si se modifican los datos, se negará a actualización. Por ejemplo, cas es un bloqueo optimista. , pero estrictamente hablando, no es un bloqueo. La atomicidad se usa para garantizar la sincronización de datos. Por ejemplo, el bloqueo optimista de la base de datos se implementa a través del control de versiones. Cas no garantiza el hilo sincronización Optimista, no hay otra influencia de subprocesos durante la actualización de datos

Resumen: los bloqueos pesimistas son adecuados para escenarios con muchas operaciones de escritura, los bloqueos optimistas son adecuados para escenarios con muchas operaciones de lectura y el rendimiento de los bloqueos optimistas será mayor que el de los bloqueos pesimistas

2. Bloqueos compartidos, bloqueos exclusivos (bloqueos de lectura y escritura, bloqueos mutex) 

El bloqueo de lectura y escritura es una tecnología: implementada por la clase ReentrantReadWriteLock

Para mejorar el rendimiento, Java proporciona bloqueos de lectura y escritura. Los bloqueos de lectura se usan para leer y los bloqueos de escritura se usan para escribir. Control flexible. Si no hay bloqueo de escritura, la lectura no bloquea, lo que mejora el programa a un cierta medida eficiencia de ejecución.

Los bloqueos de lectura y escritura se dividen en bloqueos de lectura y bloqueos de escritura. Los bloqueos de lectura múltiple no se excluyen mutuamente, y los bloqueos de lectura y escritura se excluyen mutuamente. Esto lo controla la propia JVM.

Bloqueo de lectura (bloqueo compartido) : permite que varios subprocesos adquieran bloqueos de lectura y accedan al mismo recurso al mismo tiempo

Bloqueo de escritura (exclusivo) : solo se permite que un subproceso adquiera un bloqueo de escritura y no se permite el acceso simultáneo al mismo recurso

Bloqueo compartido : también llamado bloqueo de lectura, que puede ver datos, pero no puede modificar o eliminar un bloqueo de datos. Después del bloqueo, otros usuarios pueden leer simultáneamente, pero no pueden modificar, agregar o eliminar datos. Este bloqueo puede ser utilizado por múltiples subprocesos. , para compartir datos de recursos

Bloqueo exclusivo : también llamado bloqueo exclusivo, bloqueo de escritura, bloqueo exclusivo, bloqueo exclusivo, el bloqueo solo puede ser mantenido por un subproceso a la vez, después del bloqueo, cualquier subproceso que intente bloquear nuevamente se bloqueará hasta que el subproceso actual se desbloquee

Por ejemplo: después de que el subproceso A agrega un bloqueo exclusivo a los datos, otros subprocesos ya no pueden agregar ningún tipo de bloqueo a los datos, y el subproceso que adquiere el bloqueo mutex puede leer y modificar datos.

Bloqueo de lectura y escritura en Java: ReadWriteLock , clase de implementación ReadWriteLock ReentrantReadWriteLock

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 独占锁(写锁) 一次只能被一个线程占有
 * 共享锁(读锁) 多个线程可以同时占有
 * ReadWriteLock;其实现类 ReentrantReadWriteLock
 * 读-读  可以共存!
 * 读-写  不能共存!
 * 写-写  不能共存!
 */
public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        // 写入
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.put(temp + "",temp + "");
            },String.valueOf(i)).start();
        }

        // 读取
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp + "");
            },String.valueOf(i)).start();
        }
    }
}

class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();

    // 读写锁
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock lock = new ReentrantLock();

    // 存、写数据,只希望同时只有一个线程写
    public void put(String key,Object value){
        readWriteLock.writeLock().lock();

        try{
            System.out.println(Thread.currentThread().getName() + "写入" + key);

            map.put(key,value);

            System.out.println(Thread.currentThread().getName() + "写入OK");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    // 取,读数据,所有线程都可以同时读取
    public void get(String key){
        readWriteLock.readLock().lock();

        try{
            System.out.println(Thread.currentThread().getName() + "读取" + key);

            map.get(key);

            System.out.println(Thread.currentThread().getName() + "读取OK");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

producción

1写入1
1写入OK
2写入2
2写入OK
3写入3
3写入OK
4写入4
4写入OK
5写入5
5写入OK
1读取1
2读取2
2读取OK
3读取3
1读取OK
3读取OK
5读取5
5读取OK
4读取4
4读取OK

1-5 Al escribir datos, el orden de ejecución no será reemplazado por otros subprocesos, pero al leer datos, saltará en la cola 

3. Cerradura justa e injusta

Bloqueo justo : hay varios subprocesos para adquirir bloqueos en el orden en que se aplican para bloqueos, es decir, si en un grupo de subprocesos, se puede garantizar que cada subproceso obtenga el bloqueo, por ejemplo: ReentrantLock (cola síncrona FIFO utilizada)

Bloqueos injustos : la forma de adquirir bloqueos es aleatoria. No hay garantía de que cada subproceso pueda obtener el bloqueo. Algunos subprocesos morirán de hambre y nunca obtendrán el bloqueo. Por ejemplo: sincronizado, ReentrantLock

Resumen: el rendimiento de los bloqueos no justos es mayor que el de los bloqueos justos y puede reutilizar el tiempo de la CPU

4. Bloqueo reentrante (bloqueo recursivo)

Bloqueo reentrante: también llamado bloqueo recursivo, después de que la capa externa usa el bloqueo, la capa interna aún se puede usar sin interbloqueo

Bloqueo no reentrante: cuando el subproceso actual ejecuta un método y ha adquirido el bloqueo, cuando intente adquirir el bloqueo nuevamente en el método, no se bloqueará

1. Explicación de Zhihu: El bloqueo reentrante se refiere al código de que la función recursiva interna aún puede adquirir el bloqueo después de que la función externa del mismo subproceso adquiera el bloqueo. Cuando el mismo subproceso adquiere el bloqueo en el método externo, al ingresar al interior El método adquirirá automáticamente el bloqueo. Es decir, un subproceso puede ingresar cualquier bloque de código sincronizado que ya posea.

 2. La fuerte explicación de Coffey: el bloqueo reentrante se refiere a la unidad de subproceso.Después de que un subproceso adquiere el bloqueo del objeto, el subproceso puede adquirir el bloqueo sobre el objeto nuevamente, mientras que otros subprocesos no pueden.

Tanto sincronizado como ReentrantLock son bloqueos reentrantes

Uno de los significados de los bloqueos reentrantes es evitar interbloqueos.

1, sincronizado

/**
 * 可重入锁(递归锁)
 *      在外层使用锁之后,在内层仍然可以使用,并且不会产生死锁(前提是同一把锁,如同一个类、同一个实例、同一个代码块)
 *      1、来自知乎的解释:可重入锁指的是同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。也就是说,线程可以进入任何一个他已经拥有锁的所有同步代码块。
 *      2、Coffey强的解释:可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
 *      synchronized 和 ReentrantLock 都是可重入锁
 *      可重入锁的意义之一在于防止死锁
 * 不可重入锁:在当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
 */
public class SynchronizedTest {
    public static void main(String[] args) {
        Object obj = new Object();

        new Thread(() -> {
            // 第一次加锁
            synchronized (obj){
                System.out.println(Thread.currentThread().getName() + "线程执行第一层");
                // 第二次加锁,此时obj对象处于锁定状态,但是当前线程仍然可以进入,避免死锁
                synchronized (obj){
                    // 抛异常
                    int a = 10/0;
                    System.out.println(Thread.currentThread().getName() + "线程执行第二层");
                }
            }
        },"t1").start();

        new Thread(() -> {
            // 第一次加锁
            synchronized (obj){
                System.out.println(Thread.currentThread().getName() + "线程执行第一层");
                // 第二次加锁,此时obj对象处于锁定状态,但是当前线程仍然可以进入,避免死锁
                synchronized (obj){
                    System.out.println(Thread.currentThread().getName() + "线程执行第二层");
                }
            }
        },"t2").start();
    }
}

Hay una excepción en la segunda capa del subproceso t1. Use la palabra clave sincronizada. Si ocurre una excepción, el bloqueo se liberará automáticamente y el subproceso t2 se generará normalmente. 

2, bloqueo de reentrada

/**
 * 可重入锁(递归锁)
 *      synchronized 和 ReentrantLock 都是可重入锁
 *      可重入锁的意义之一在于防止死锁
 */
public class ReentrantLockTest {
    public static void main(String[] args) {
        // 非公平锁
        Lock lock = new ReentrantLock(false);

        new Thread(() -> {
            // 第一次加锁
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName() + "线程执行第一层");

                // 第二次加锁,此时obj对象处于锁定状态,但是当前线程仍然可以进入,避免死锁
                lock.lock();
                try {
                    // 抛异常
                    int a = 10/0;
                    System.out.println(Thread.currentThread().getName() + "线程执行第二层");
                } finally {
                    lock.unlock();
                }
            } finally { // Lock是显式锁,必须手动关闭锁(忘记关闭锁会导致 死锁)
                lock.unlock();
            }
        },"t1").start();

        new Thread(() -> {
            // 第一次加锁
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName() + "线程执行第一层");

                // 第二次加锁,此时obj对象处于锁定状态,但是当前线程仍然可以进入,避免死锁
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "线程执行第二层");
                } finally {
                    lock.unlock();
                }
            } finally { // Lock是显式锁,必须手动关闭锁(忘记关闭锁会导致 死锁)
                lock.unlock();
            }
        },"t2").start();
    }
}

Hay una excepción en la segunda capa del subproceso t1. Nuestro bloqueo de liberación está escrito finalmente, lo que no afecta la salida normal del subproceso t2. 

Nota:

1. El bloqueo es un bloqueo explícito, y el bloqueo debe cerrarse manualmente (abrir y cerrar el bloqueo manualmente, olvidarse de cerrar el bloqueo provocará un punto muerto) sincronizado es un          bloqueo implícito, que liberará automáticamente el bloqueo, y se liberará automáticamente cuando está fuera de alcance

2. El bloqueo debe cerrarse y liberarse manualmente, y debe liberarse en la cláusula final.

Comentarios de código fuente en la clase ReentrantLock

Cinco, bloqueo de giro

Spin lock: cuando un subproceso adquiere un bloqueo, si el bloqueo ha sido adquirido por otros subprocesos, entonces el subproceso esperará en un bucle y luego evaluará continuamente si el bloqueo se puede adquirir con éxito y no saldrá del bucle hasta que el bloqueo se adquiere, como máximo en cualquier momento Solo una unidad de ejecución puede adquirir el bloqueo

Resumen: No habrá cambio de estado de subproceso, y siempre estará en el estado de usuario, lo que reduce el consumo de cambio de contexto de subproceso. La desventaja es que el bucle consumirá CPU

Seis, candado pesado, candado ligero

Un bloqueo de peso pesado es un título: sincronizado se implementa a través de un bloqueo de monitor (monitor) dentro del objeto, y el bloqueo de monitor en sí se basa en el bloqueo Mutex del sistema operativo subyacente para implementar.

El sistema operativo necesita cambiar del modo de usuario al modo central para cambiar los subprocesos, lo cual es muy costoso. Este tipo de bloqueo que se basa en el sistema operativo Mutex Lock para implementarse se denomina bloqueo de peso pesado. Para optimizar la sincronización, se introducen bloqueos ligeros y bloqueos sesgados.

Cerraduras pesadas en Java: sincronizadas

El bloqueo ligero es un mecanismo de optimización de bloqueo agregado en JDK6: el bloqueo ligero utiliza la operación CAS para eliminar el mutex utilizado por la sincronización sin competencia. El peso ligero es relativo a los bloqueos de peso pesado implementados mediante mutexes del sistema operativo. Los candados ligeros reducen el consumo de rendimiento de los candados pesados ​​tradicionales mediante mutexes del sistema operativo sin competencia multiproceso. Si más de dos hilos compiten por el mismo candado, el candado liviano no será efectivo y debe expandirse a un candado pesado.

Ventajas: si no hay competencia, la operación CAS evita con éxito la sobrecarga de usar un mutex.

Desventajas: Si hay competencia, además de la sobrecarga del propio mutex, se genera una sobrecarga adicional de la operación CAS, por lo tanto, en el caso de competencia, las cerraduras ligeras son más lentas que las cerraduras pesadas tradicionales.

Siete, bloqueo de polarización

El bloqueo sesgado es un mecanismo de optimización de bloqueo agregado en JDK6: en el caso de que no haya competencia, se elimina toda la sincronización e incluso la operación CAS no se realiza. La parcialidad se refiere a la excentricidad, lo que significa que el bloqueo estará sesgado hacia el primer subproceso para obtenerlo. Si el bloqueo no ha sido adquirido por otros subprocesos durante el siguiente proceso de ejecución, el subproceso que contiene el bloqueo sesgado se bloqueará para siempre. No más se requiere sincronización. Cada vez que un subproceso que contiene un bloqueo sesgado ingresa a un bloque de sincronización relacionado con este bloqueo, la máquina virtual ya no puede realizar ninguna operación de sincronización (como operaciones de bloqueo, desbloqueo y actualización en Mark Word, etc.).

Ventajas: Elimina toda la sincronización, incluso no se realiza la operación CAS, que es mejor que las cerraduras ligeras.

Desventajas: si la mayoría de los bloqueos en el programa siempre son accedidos por varios subprocesos diferentes, los bloqueos sesgados son redundantes.

Ocho, bloqueo de segmento

El bloqueo de segmento es un mecanismo: el mejor ejemplo para ilustrar el bloqueo de segmento es ConcurrentHashMap. El principio de ConcurrentHashMap: Subdivide internamente varios HashMaps pequeños, llamados segmentos (Segment). De forma predeterminada, un ConcurrentHashMap se subdivide en 16 segmentos, que es la concurrencia de bloqueos. Si necesita agregar un valor-clave a ConcurrentHashMap, en lugar de bloquear todo el HashMap, primero obtiene el segmento en el que se debe almacenar el valor-clave de acuerdo con el código hash, luego bloquea el segmento y completa la operación de colocación. En un entorno de subprocesos múltiples, si varios subprocesos realizan operaciones de colocación al mismo tiempo, siempre que el valor-clave agregado no se almacene en el mismo segmento, los subprocesos pueden ser verdaderamente paralelos.

Seguridad de subprocesos: ConcurrentHashMap es una matriz de segmento, y el segmento está bloqueado al heredar ReentrantLock, por lo que cada operación que debe bloquearse bloquea un segmento, siempre que cada segmento sea seguro para subprocesos, seguridad global para subprocesos

Nueve, mutex, bloqueo de sincronización

Los bloqueos mutex son sinónimos de bloqueos pesimistas y bloqueos exclusivos, lo que significa que solo un subproceso puede acceder a un recurso y otros subprocesos no pueden acceder a él.

lectura-lectura mutex

exclusión mutua de lectura y escritura

exclusión mutua de escritura y lectura

escritura-escritura mutex

Bloqueos de sincronización en Java: sincronizados

Un bloqueo de sincronización es sinónimo de mutex, lo que significa que varios subprocesos se ejecutan al mismo tiempo y solo un subproceso puede acceder a los datos compartidos al mismo tiempo.

Bloqueos de sincronización en Java: sincronizados

10. Punto muerto

El interbloqueo es un fenómeno: si el hilo A tiene el recurso x, el hilo B tiene el recurso y, el hilo A espera que el hilo B libere el recurso y, el hilo B espera que el hilo A libere el recurso x, ninguno de los hilos libera sus propios recursos, los dos hilos no pueden obtener los recursos de los demás, lo que provocará un punto muerto.

El interbloqueo en Java no se puede romper por sí mismo, por lo que después de que el subproceso se bloquea, el subproceso no puede responder. Por lo tanto, debemos prestar atención a la escena concurrente del programa para evitar un punto muerto.

Once, engrosamiento de bloqueo

El engrosamiento de bloqueo es una técnica de optimización: si una serie de operaciones continuas bloquean y desbloquean repetidamente el mismo objeto, e incluso se producen operaciones de bloqueo 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 de rendimiento innecesaria. por lo que se adopta una solución: expandir (aproximado) el alcance del bloqueo al exterior de toda la secuencia de operación, de modo que la frecuencia de bloqueo y desbloqueo se reduzca en gran medida, lo que reduce la pérdida de rendimiento

12. Eliminación de bloqueo

La eliminación de bloqueo es una técnica de optimización: simplemente elimina el bloqueo. La eliminación de bloqueos se puede realizar cuando la máquina virtual de Java descubre que algunos datos compartidos no serán competido por subprocesos cuando se está ejecutando.

¿Juzgando que los datos compartidos no serán competido por subprocesos?

Utilice la tecnología de análisis de escape: analice el alcance del objeto. Si el objeto se define en el método A y se pasa como un parámetro al método B, se llama método de escape; si otros subprocesos acceden a él, se llama escape de subproceso.

Ciertos datos en el montón no se escaparán y otros subprocesos accederán a ellos, por lo que pueden tratarse como datos en la pila, y se consideran privados para el subproceso, y no se necesita bloqueo síncrono.

trece, sincronizado

Synchronized es una palabra clave en Java: se utiliza para modificar métodos e instancias de objetos

sincronizado pertenece al bloqueo exclusivo, bloqueo pesimista, bloqueo reentrante, bloqueo injusto

1. Al actuar sobre un método de instancia, lo que se bloquea es la instancia del objeto (este);

2. Cuando se usa como método estático, la clase Class está bloqueada, lo que equivale a un bloqueo global de la clase, y bloqueará todos los subprocesos que llamen al método;

3. Cuando la sincronización actúa en una instancia de objeto que no es NULL, todos los bloques de código que bloquean el objeto están bloqueados. Tiene varias colas.Cuando varios subprocesos acceden juntos a un monitor de objetos, el monitor de objetos almacenará estos subprocesos en diferentes contenedores.

Cada objeto tiene un objeto monitor . El bloqueo es para competir por los objetos monitor . El bloqueo del bloque de código se logra agregando instrucciones monitorenter y monitorexit antes y después del bloque de código. El bloqueo del método se juzga por un bit indicador

Catorce, la diferencia entre Lock y sincronizado

Lock: es una interfaz en Java, bloqueo reentrante, bloqueo pesimista, bloqueo exclusivo, bloqueo mutex, bloqueo de sincronización

1. El bloqueo debe adquirir y liberar manualmente el bloqueo. Es como la diferencia entre automático y manual.

2. Lock es una interfaz y sincronizado es una palabra clave en Java, y sincronizado es una implementación de lenguaje integrada.

3. Synchronized liberará automáticamente el bloqueo ocupado por el subproceso cuando ocurra una excepción, por lo que no causará un fenómeno de interbloqueo; mientras que Lock, si ocurre una excepción, si no libera activamente el bloqueo a través de unLock(), es probable para causar un fenómeno de punto muerto, por lo que debe liberar el bloqueo en el bloque finalmente al usar Bloquear.

4. El bloqueo puede hacer que el subproceso que espera el bloqueo responda a la interrupción, pero sincronizado no puede. Cuando se usa sincronizado, el subproceso en espera esperará para siempre y no puede responder a la interrupción.

5. A través de Lock, puede saber si el bloqueo se ha adquirido con éxito, pero sincronizado no puede.

6. El bloqueo puede mejorar la eficiencia de varios subprocesos para operaciones de lectura mediante la implementación de bloqueos de lectura y escritura.

Ventajas de sincronizado:

Cuando solo se necesitan funciones básicas de sincronización, use sincronizado

El bloqueo debe garantizar que el bloqueo se libere en el bloque final. Si usa sincronizado, la JVM garantiza que incluso si ocurre una excepción, el bloqueo se libera automáticamente.

Al usar Lock, es difícil para la máquina virtual Java saber qué objetos de bloqueo están retenidos por un bloqueo de subproceso específico.

15. La diferencia entre ReentrantLock y sincronizado

ReentrantLock es una clase en Java: hereda la clase Lock, bloqueo reentrante, bloqueo pesimista, bloqueo exclusivo, bloqueo mutex, bloqueo de sincronización

Mismo punto:

1. Resolver principalmente el problema de cómo acceder a las variables compartidas de forma segura.

2. Todos son bloqueos reentrantes, también llamados bloqueos recursivos.El mismo subproceso puede adquirir el mismo bloqueo varias veces.

3. Garantizar las dos características de seguridad del hilo: visibilidad y atomicidad

diferencia:

1. ReentrantLock es como un automóvil manual, que necesita llamar explícitamente a los métodos de bloqueo y desbloqueo, y sincronizado obtiene implícitamente el bloqueo de liberación.

2. ReentrantLock puede responder a las interrupciones y sincronizado no puede responder a las interrupciones. ReentrantLock proporciona una mayor flexibilidad para lidiar con la indisponibilidad de bloqueo

3. ReentrantLock está en el nivel de API y sincronizado está en el nivel de JVM

4. ReentrantLock puede implementar bloqueos justos y bloqueos injustos. El valor predeterminado es bloqueos injustos. sincronizado es un bloqueo injusto y no se puede cambiar.

5. ReentrantLock puede vincular múltiples condiciones a través de Condition

Puede referirse a

Lo llevará a una comprensión profunda de 21 tipos de bloqueos en Java

¿Qué son los bloqueos en Java? Blog de _zhangjia_happy-CSDN Blog_¿Cuáles son los tipos de bloqueos de Java?

Supongo que te gusta

Origin blog.csdn.net/MinggeQingchun/article/details/127383395
Recomendado
Clasificación