Cómo logra Java la seguridad de subprocesos en un entorno de subprocesos múltiples

prefacio

Este blog resumirá Java multithreading basado en el conocimiento existente. El siguiente blog es solo un resumen del proceso de aprendizaje personal. Es un gran honor ayudar a todos los bloggers.
Este blog presentará brevemente el concepto de seguridad de subprocesos y varios métodos para practicar la seguridad de subprocesos. Se centrará en varias operaciones de bloqueo de uso común y lo complementará y modificará más adelante con más estudios.

Riesgos que se enfrentan en un entorno de subprocesos múltiples

Como se mencionó anteriormente, la introducción de subprocesos múltiples mejora en gran medida la eficiencia de la ejecución de una tarea, pero por otro lado, también presenta algunos riesgos al tiempo que mejora la eficiencia. Por ejemplo, el siguiente
escenario

//利用多线程,对同一个静态变量做++操作
public static int COUNT = 0;
    public static void main(String[] args) {
    
    
        Thread[] threads = new Thread[20];//创建一个线程数组同于存放接下来创建的线程
        for(int i = 0; i < threads.length; i++){
    
    
            threads[i] = new Thread(new Runnable() {
    
    //使用匿名内部类创建线程
                @Override
                public void run() {
    
    
                    for(int i = 0; i < 1000;i++) {
    
    
                        COUNT++;
                    }
                }
            });
        }

        for(Thread t : threads){
    
    //依次启动这20个线程
            t.start();
        }

        while (Thread.activeCount() > 2){
    
    
            Thread.yield();//保证Thread数组中的所有线程都跑完
        }
        
        System.out.println(COUNT);
    }

inserte la descripción de la imagen aquí
El código anterior implementa la operación ++ en la misma variable estática en un entorno de subprocesos múltiples para
crear un total de 20 subprocesos, y cada subproceso ejecuta COUNT++ 1000 veces. En teoría, cuando se ejecutan los 20 subprocesos, el valor de COUNT será Become 20000
en realidad se ha ejecutado tres veces, pero el valor de COUNT no ha llegado a 20000 y el valor es diferente cada vez, ¿por qué?

El análisis causa problemas de seguridad de subprocesos

Para el código anterior, pasó por el siguiente proceso después del inicio:
iniciar --> ejecutar el proceso java.exe --> inicializar los parámetros de JVM --> crear una máquina virtual JVM --> iniciar el subproceso en segundo plano --> iniciar el nivel principal de Java subproceso (Empezar a ejecutar el método principal en Java)
inserte la descripción de la imagen aquí
Cuando el subproceso subproceso comienza a ejecutar COUNT++ del método de ejecución, se desmontará en una ejecución de tres pasos

  1. Obtenga COUNT del área de método
  2. Modificar el valor de COUNT a COUNT+1 en la memoria de trabajo
  3. Escribe el valor de COUNT en la memoria principal

Riesgo: en este momento, debido a que habrá 20 subprocesos de subprocesos ejecutándose al mismo tiempo, puede suceder que la memoria de trabajo de los dos subprocesos obtenga COUNT al mismo tiempo (es decir, el valor de COUNT obtenido es el mismo), entonces en este momento, la ejecución de los dos subprocesos se completa y se vuelve a escribir en la memoria principal Después, el valor de COUNT es solo +1 y no alcanza el +2 esperado, y la seguridad del subproceso aparece en este momento.
inserte la descripción de la imagen aquí
La causa raíz de la seguridad de subprocesos: varios subprocesos modifican y reescriben el mismo segmento de memoria, lo que da como resultado que el contenido modificado no se pueda modificar realmente.

Razones por las que los subprocesos múltiples no son seguros

  1. Atomicity
    Instrucciones de varias líneas, si hay una dependencia antes y después de la instrucción, no se pueden insertar otras instrucciones que afecten el resultado de ejecución de su propio hilo
  2. Visibilidad
    En el subproceso de ejecución de la CPU de la llamada al sistema, la modificación de una variable compartida por un subproceso puede ser vista por otro subproceso inmediatamente
  3. Orden
    El orden en que se ejecuta el programa se ejecuta en el orden del código (el procesador puede reordenar las instrucciones)

Una solución al problema de la seguridad de los hilos

palabra clave sincronizada

En Java, la palabra clave sincronizada se utiliza para controlar subprocesos sincrónicos. En un entorno de subprocesos múltiples, los bloques de código, las clases, los métodos y las variables modificadas por sincronizado no pueden ejecutarse mediante subprocesos al mismo tiempo.

Antes de jdk1.6, el bloqueo integrado de Java sincronizado no tenía el mecanismo de expansión de bloqueo de bloqueo de polarización -> bloqueo ligero -> bloqueo de peso pesado. El
mecanismo de expansión de bloqueo se implementó después de 1.6 para optimizar el rendimiento de sincronización de subprocesos de Java. Antes de la versión 1.6, era un candado pesado basado en el mecanismo del monitor.

El funcionamiento específico de sincronizado

La palabra clave sincronizada se agrega a métodos estáticos y bloques de código para bloquear la clase, y se agrega a métodos de instancia y bloques de código para bloquear la instancia del objeto.

public class Singleton {
    
    
    private volatile static Singleton instance;

    private Singleton() {
    
    
    }

    public static Singleton getInstance(){
    
    
        if(instance == null){
    
    
            synchronized (Singleton.class){
    
    
                instance = new Singleton();
            }
        }
        return instance;
    }
}

En el modo singleton, también es muy importante modificar la instancia con la palabra clave volatile, lo que evita el reordenamiento del paso de inicialización y la introducción del paso de asignación en el nuevo Singleton().

El principio de implementación subyacente de sincronización

Darse cuenta de la exclusión mutua de sincronización de subprocesos múltiples de un programa (una pieza de código, en cualquier momento, solo se puede ejecutar un subproceso)

Cuando el bloque de código modificado sincronizado se descompila en código de bytes, la palabra monitor se agrega antes y después del bloque de código. El frente es monitoreneter, y el reverso es monitorexit. Los dos signos marcan respectivamente la adquisición del bloqueo y la liberación del bloqueo.

Cuando se ejecuta el comando monitorenter, el subproceso actual intentará obtener el derecho a mantener el monitor correspondiente al bloqueo del objeto. Cuando el contador del monitor del bloqueo del objeto es 0, entonces el subproceso puede obtener el monitor, establecer el contador a 1, y adquiera con éxito el bloqueo del objeto. Si el subproceso actual ya tiene derecho a mantener el monitor del bloqueo del objeto, puede volver a ingresar al monitor y el contador aumentará en 1 cuando vuelva a ingresar. Si otros subprocesos ya poseen la propiedad del monitor del bloqueo del objeto, el subproceso se bloqueará hasta que finalice la ejecución del subproceso que adquiere el bloqueo del objeto, es decir, se ejecuta la instrucción monitorexit. Después de la ejecución, el bloqueo del objeto se liberará y el contador se pondrá a 0.

El motivo de las dos banderas del monitor

Si solo hay un indicador de monitor para obtener el bloqueo de objeto, el bloqueo de objeto aún no se liberará normalmente después de salir del programa en circunstancias anormales, lo que conducirá a un interbloqueo, por lo que si se establece el último indicador de monitor, no importa cómo esté el programa. sale, eventualmente se ejecutará para monitorearsalir, libere el bloqueo aquí

El principio del bloqueo de reentrada sincronizado

El bloqueo de reentrada es hacer que el mismo hilo adquiera un bloqueo de objeto repetidamente. La capa inferior se basa en un contador. Cada vez que se adquiere, será +1, y una vez que se libere, será -1

girar

En escenarios reales, muchos objetos que están bloqueados por sincronización se ejecutan muy rápidamente. En este momento, si todos los demás subprocesos se establecen en el estado bloqueado, llevará mucho tiempo cambiar entre el estado del usuario y el estado del núcleo. Por lo tanto, se introduce la operación de giro, de modo que el subproceso que no se ha apropiado del bloqueo sigue girando e intenta constantemente adquirir el bloqueo, mejorando así la eficiencia.

El principio subyacente de la actualización de bloqueo sincronizado

Hay un campo threadid en el encabezado del objeto del objeto de bloqueo.Cuando se accede al threadid por primera vez, el threadid está vacío, y la JVM le permite mantener un bloqueo sesgado, y establece el threadid como su threadid.Cuando ingresando nuevamente, primero juzgará si el threadid está relacionado con el El id del hilo es el mismo, si es consistente, use el objeto directamente, si no, se actualizará a un candado liviano; el candado se adquirirá girando algunas veces, si el bloqueo no se ha adquirido después de un período de ejecución, el bloqueo se actualizará a un bloqueo de nivel de bloqueo de peso.

CAS

concepto

CAS - CompareAndSwap - comparar e intercambiar
CAS contiene tres operandos - ubicación de memoria (V), puntero esperado (A), nuevo valor a escribir (B)

Paso 1: compare si la ubicación de la memoria (V) es igual al valor esperado (A)
Paso 2: si son iguales, escriba el nuevo valor (B) que se escribirá en la ubicación de la memoria (A)
Paso 3: devuelva el valor booleano type , indicando si la operación fue exitosa

Cuando varios subprocesos realizan operaciones CAS en un determinado recurso al mismo tiempo, solo un subproceso devolverá verdadero si la operación es exitosa, y los otros subprocesos girarán y esperarán. El bloqueo optimista es una implementación típica de CAS

¿Qué problemas pueden surgir del CAS?

1. Problema ABA El problema ABA es que el valor en V cambia de A a B, y luego de B a A. Desde la perspectiva de CAS, durante este proceso de cambio, el subproceso
no está ocupado durante este período.
número (como llevar una marca de tiempo)
2. La sobrecarga de giro siempre es demasiado alta.
Cuando hay demasiados subprocesos que se apropian del mismo bloqueo, la probabilidad de giro CAS será relativamente alta, lo que desperdiciará una gran cantidad de recursos de CPU y la eficiencia en este momento se reducirá. Será más bajo que sincronizado
3. Solo puede garantizar la operación atómica de una variable compartida
. Cuando se opera en una sola variable compartida, CAS se puede usar para garantizar la atomicidad, pero cuando se opera en múltiples variables compartidas. , CAS no es aplicable. En este punto, debe bloquearse.

bloquear bloquear

Interfaz de bloqueo

Los métodos proporcionados en la interfaz de bloqueo son más extensibles que los métodos de sincronización bloqueados sincronizados y los bloques de código de sincronización. Permite una estructura más flexible, puede tener propiedades completamente diferentes y puede admitir objetos de condición de múltiples clases relacionadas. Sus ventajas
:

  • hacer cerraduras más justas
  • Hacer que los subprocesos respondan a las interrupciones mientras esperan bloqueos
  • Permite que el subproceso regrese inmediatamente o espere un período de tiempo cuando no puede adquirir el bloqueo
  • Los bloqueos se pueden adquirir y liberar en diferentes órdenes en diferentes ámbitos.
    En general, Lock es una versión extendida y mejorada de la operación de bloqueo sincronizada. Lock proporciona colas incondicionales, consultables, cronometrables, interrumpibles y de múltiples condiciones.

API de interfaz de bloqueo

1. void lock();//adquirir bloqueo
2, void lockInterruptiblemente;//el proceso de adquisición de bloqueo puede responder a la interrupción
3, booleano tryLock();//la respuesta sin bloqueo a la interrupción puede volver inmediatamente, adquirir el bloqueo devuelve verdadero de lo contrario falso
4. boolean tryLock(long time, TimeUnit unit);// Adquirir el bloqueo a lo largo del tiempo, y el bloqueo se puede adquirir dentro del tiempo de espera o sin interrupción
5. Condition newCondition(); // Obtener el componente de notificación de espera vinculado a el bloqueo, actualmente El subproceso primero debe adquirir el bloqueo antes de que pueda esperar, la espera liberará el bloqueo, y el subproceso solo puede regresar de la espera si adquiere el bloqueo nuevamente

Por lo general, se usa para mostrar el uso de bloqueo en la siguiente forma:

public class Test {
    
    
    public volatile static int COUNT = 0;
    public static void main(String[] args) {
    
    
        Lock lock = new ReentrantLock();
        Thread[] threads = new Thread[20];//创建一个线程数组同于存放接下来创建的线程
        for(int i = 0; i < threads.length; i++){
    
    
            Runnable r = new Runnable() {
    
    //使用匿名内部类创建线程
                @Override
                public void run() {
    
    
                    for(int i = 0; i < 1000;i++) {
    
    
                        lock.lock();
                        try {
    
    
                            COUNT++;
                        }finally {
    
    
                            lock.unlock();
                        }
                    }
                }
            };
            threads[i] = new Thread(r);
        }
        for(Thread t : threads){
    
    //依次启动这20个线程
            t.start();
        }

        while (Thread.activeCount() > 2){
    
    
            Thread.yield();//保证Thread数组中的所有线程都跑完
        }
        System.out.println(COUNT);
    }
}

Subclase de implementación de bloqueo

  • LeerEscribirBloquear
  • Bloqueo de reentrada
  • ReentranteReadWriteLock
  • Cerradura Estampada
concepto AQS

AQS——Abstract Queue Synchronizer es un marco para construir bloqueos y sincronizadores. AQS es adecuado para la construcción simple y eficiente de una gran cantidad de sincronizadores ampliamente utilizados, como ReentrantLock, Semaphore y otros como RenntrantReadWriteLock, SynchronousQueue, FutureTask, etc. basado en AQS

AQS implementa una serie de implementaciones subyacentes, como la gestión del estado de sincronización, la cola de subprocesos bloqueados y la espera de notificaciones.

Análisis del principio AQS

La idea central de AQS es: si el recurso compartido solicitado está inactivo, el subproceso que actualmente solicita el recurso se establece como un subproceso de trabajo efectivo y el recurso compartido se establece en un estado bloqueado. Si el recurso compartido solicitado está ocupado , se requiere un conjunto de mecanismos de asignación de bloqueos para el bloqueo de subprocesos en espera y activación AQS implementa este mecanismo mediante el bloqueo de la cola CLH (cola bidireccional virtual), y el bloqueo no se Los subprocesos se agregan a la cola.

Método de intercambio de recursos AQS

La capa inferior de AQS define dos métodos para compartir recursos:

  • Exclusivo (exclusivo): solo un hilo puede obtener recursos, como ReentrantLock, que se puede dividir en bloqueos justos y bloqueos injustos; bloqueos
    justos: según el orden en que se organizan los hilos en la cola, el primero en ingresar a la cola primero obtiene
    bloqueos injustos de recursos: cuando un subproceso quiere adquirir recursos, ignora el orden de la cola y se adelanta directamente al bloqueo
  • Compartir (compartido): se pueden ejecutar varios subprocesos al mismo tiempo, como Semaphore, CountDownLatch
ReentrantLock (bloqueo reentrante)

ReentrantLock es un bloqueo de reentrada que implementa la interfaz de bloqueo y admite la reentrada, lo que significa que puede bloquear repetidamente el recurso compartido, es decir, el subproceso actual no se bloqueará si vuelve a adquirir el bloqueo.

La palabra clave sincronizada en Java admite implícitamente la reentrada, y la palabra clave sincronizada se realiza a través del autoincremento del contador del monitor.

Principio de implementación:

  • Cuando el subproceso adquiere el bloqueo, si el subproceso que ya adquirió el bloqueo es el subproceso actual, adquirirá directamente el éxito nuevamente.
  • Dado que el bloqueo se adquirirá n veces, solo se puede considerar que el bloqueo se ha liberado con éxito después de que se haya liberado n veces.
ReadWriteLock (bloqueo de lectura y escritura)

ReadWriteLock es una interfaz de bloqueo de lectura y escritura. ReadWriteLock es una tecnología de separación utilizada para mejorar el rendimiento de los programas concurrentes. ReentrantReadWriteLock es una implementación específica de la interfaz ReadWriteLock, que realiza la separación de lectura y escritura. Los recursos se comparten cuando se leen bloqueos y exclusivos. al escribir candados

Los bloqueos de lectura y escritura tienen tres características importantes:

  • Selectividad justa: admite métodos de adquisición de bloqueos justos e injustos, y el rendimiento sigue siendo mejor que los bloqueos justos
  • Bloqueos reentrantes: tanto los bloqueos de lectura como los bloqueos de escritura admiten la reentrada de subprocesos
  • Degradación de bloqueo: siga el orden de adquirir bloqueos de escritura, adquirir bloqueos de lectura y liberar bloqueos de escritura, los bloqueos de escritura se pueden degradar a bloqueos de lectura

ThreadLocal

ThreadLocal se utiliza para proporcionar variables locales de subprocesos, lo que puede garantizar que las variables de cada subproceso sean independientes de las variables de otros subprocesos en un entorno de subprocesos múltiples. Se puede entender que ThreadLocal crea una copia separada de las variables compartidas para cada hilo y no se afecta entre sí.

El uso de ThreadLocal en múltiples subprocesos es operar variables independientes de sus propios subprocesos, y los subprocesos no están relacionados

ThreadLocal es para garantizar la independencia de los datos en un entorno de subprocesos múltiples

Instancia de aplicación ThreadLocal

public class Test {
    
    
    private static String commStr;
    private static ThreadLocal<String> threadStr = new ThreadLocal<String>();
    public static void main(String[] args) {
    
    
        commStr = "main";
        threadStr.set("main");
        Thread thread = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                commStr = "thread";
                threadStr.set("thread");
                System.out.println("线程"+Thread.currentThread().getName()+":");
                System.out.println("commStr:"+commStr);
                System.out.println("threadStr:"+threadStr.get());
            }
        });
        thread.start();
        try {
    
    
            thread.join();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("线程"+Thread.currentThread().getName()+":");
        System.out.println("commStr:"+commStr);
        System.out.println("threadStr:"+threadStr.get());
    }
}

Resultados de la
inserte la descripción de la imagen aquí

El principio subyacente de ThreadLocal

En esencia, ThreadLocal permite que ThreadHashMap de cada subproceso mantenga su propia copia de las variables compartidas, de modo que cada subproceso sea independiente.

  • Hay una estructura de datos ThreadHashMap en la clase Thread, que se usa para guardar las variables del objeto thread.
  • Los métodos get (), put () y remove () obtendrán el subproceso actual y luego obtendrán el ThreadHashMap a través del subproceso actual. Si ThreadHashMap es nulo, se creará un nuevo ThreadHashMap para el subproceso actual.
  • Al usar la variable de tipo ThreadLocal para operar, el ThreadHashMap se obtendrá a través del subproceso actual para operar. El ThreadHashMap de cada subproceso pertenece al subproceso en sí, y los valores mantenidos en el ThreadHashMap también pertenecen al subproceso en sí, lo que garantiza que las variables de tipo ThreadLocal sean independientes en cada subproceso, y las operaciones simultáneas de varios subprocesos no se afectarán entre sí.

Grupo de subprocesos

El grupo de subprocesos se puede comparar con el concepto del grupo de constantes en JMM, en el que un cierto número de subprocesos se almacenan por adelantado. Cuando un programa debe ejecutarse bajo un determinado subproceso, el grupo de subprocesos asignará un subproceso para ejecutar el programa correspondiente.
La introducción del grupo de subprocesos resuelve la desventaja de consumir mucho rendimiento para crear y destruir subprocesos cada vez que se ejecuta el programa, y ​​reduce la pérdida de cada inicio y destrucción de subprocesos.

El flujo de ejecución principal del grupo de subprocesos

inserte la descripción de la imagen aquí

  1. El grupo de subprocesos juzga si los subprocesos en el grupo de subprocesos principales están todos en el estado de trabajo, si no crea un subproceso de trabajo para realizar la tarea, si es así, ingrese el siguiente proceso
  2. El grupo de subprocesos juzga si la cola de trabajo está llena, si no, almacena la tarea recién enviada en la cola de trabajo, de lo contrario ingresa al siguiente proceso
  3. El grupo de subprocesos juzga si todos los subprocesos en el grupo están en el estado de trabajo. Si no se crea un nuevo subproceso para realizar la tarea, de lo contrario, será manejado por la estrategia de saturación.

Cuatro grupos de subprocesos comunes creados por la clase Executors

Se proporcionan algunos métodos de fábrica estáticos en la clase de herramientas Executors para generar algunos grupos de subprocesos de uso común:

  1. newSingleThreadExecutor: cree un grupo de subprocesos de un solo subproceso. Solo un subproceso está trabajando en este grupo de subprocesos, lo que equivale a ejecutar todas las tareas en serie con un solo subproceso
  2. newFIxedThreadPool: cree un grupo de subprocesos de tamaño fijo, y cada vez que se envíe una tarea, se creará un subproceso hasta que el subproceso alcance el valor máximo del grupo de subprocesos
  3. newCachedThreadPool: Cree un grupo de subprocesos almacenables en caché. Si el tamaño del grupo de subprocesos supera los subprocesos necesarios para procesar tareas, algunos subprocesos inactivos (subprocesos que no realizan tareas en 60 segundos) se reciclarán. Cuando aumente el número de tareas, este subproceso También puede agregar de manera inteligente nuevos subprocesos para procesar tareas. Este grupo de subprocesos no limita el tamaño de los subprocesos. El tamaño del grupo de subprocesos depende del tamaño del subproceso más grande que el sistema operativo puede crear.
  4. newScheduleThreadPool: cree un grupo de subprocesos de tamaño ilimitado, que admita el tiempo y la ejecución periódica de tareas

ThreadPoolExecutor para crear un grupo de subprocesos

ThreadPoolExecutor solo tiene una forma de crear un grupo de subprocesos: crear un grupo de subprocesos mediante parámetros personalizados

ThreadPoolExecutor pool = new ThreadPoolExecutor(
                4,//corePoolSize:核心线程数
                10,//maximumPoolSize:最大线程数(核心线程+临时线程)
                60,//keepAliveTime:空闲时间数(临时线程可空闲的最长时间,超过该时间临时线程就会被销毁)
                TimeUnit.SECONDS,//unit:时间单位
        new ArrayBlockingQueue<>(1000),//workQueue:阻塞队列(存放线程的容器)
                new ThreadFactory(){
    
    //threadFactory:匿名内部类
                    @Override
                    public Thread newThread(Runnable r){
    
    
                        //线程的工厂类
                        return new Thread(r);
                    }
                },
                //handler:拒绝策略
                //1. new ThreadPoolExecutor.AbortPolicy()//抛异常的方式
                //2. new ThreadPoolExecutor.CallerRunsPolicy()//
                //3. new ThreadPoolExecutor.DiscardOldestPolicy()//把阻塞队列存放时间最久的任务丢弃
                //4.
                new ThreadPoolExecutor.DiscardPolicy());//不处理该任务,直接丢弃

Parámetros básicos:

  • corePoolSize: la cantidad de subprocesos principales, la cantidad mínima de subprocesos definidos por subprocesos que pueden ejecutarse al mismo tiempo
  • maximumPoolSize: el número máximo de subprocesos de trabajo permitidos en el grupo de subprocesos
  • workQueue: Contenedor para almacenar tareas a ejecutar, si ingresa una nueva tarea y todos los hilos están funcionando en este momento, la tarea se almacenará en el contenedor y esperará a que un hilo inactivo la saque.

Ejemplo de uso de un grupo de subprocesos

public class Test {
    
    
    public static int COUNT;
    public static void main(String[] args) {
    
    
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                10,
                30,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000),
                new ThreadFactory() {
    
    
                    @Override
                    public Thread newThread(Runnable r) {
    
    
                        return new Thread(r);
                    }
                },
                new ThreadPoolExecutor.DiscardPolicy());


        Lock lock = new ReentrantLock();
        Runnable r = new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 1000; i++) {
    
    
                    lock.lock();
                    try {
    
    
                        COUNT++;
                    } finally {
    
    
                        lock.unlock();
                    }
                }
            }
        };


        for(int i = 0 ;i < 20; i++) {
    
    
            pool.execute(r);
        }
        while (pool.getActiveCount() > 0){
    
    
            Thread.yield();
        }
        pool.shutdown();
        System.out.println(COUNT);
    }
}

Resultados de la
inserte la descripción de la imagen aquí

punto muerto

Interbloqueo de subprocesos se refiere a un bucle infinito causado por dos o más subprocesos que retienen los recursos del otro y no los liberan activamente durante la ejecución. Estos subprocesos/procesos que siempre están esperando el uno al otro se denominan interbloqueos.

ejemplo de interbloqueo

public class DeadLock {
    
    
    private static Integer A = 0;
    private static Integer B = 10;

    public static void main(String[] args) {
    
    
        deadLock();
    }

    private static void deadLock() {
    
    
        Thread threadA = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("线程A:正在执行...");
                System.out.println("线程A:开始获取A对象锁...");
                synchronized (A){
    
    
                    System.out.println("线程A:获取A对象锁成功");
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println("线程A:线程A开始获取B对象...");
                    System.out.println("线程A:获取B对象锁成功");
                    synchronized (B){
    
    
                        Integer t = A;
                        A = B;
                        B = t;
                    }
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("线程B:正在执行...");
                System.out.println("线程B:开始获取B对象锁...");
                synchronized (B){
    
    
                    System.out.println("线程B:获取B对象锁成功");
                    System.out.println("线程B:开始获取A对象锁...");
                    synchronized (A){
    
    
                        System.out.println("线程B:获取A对象锁成功");
                        System.out.println(A);
                        System.out.println(B);
                    }
                }
            }
        });

        threadA.start();
        threadB.start();
    }
}

inserte la descripción de la imagen aquí

Cuatro condiciones necesarias para causar interbloqueo

  1. Condiciones de exclusión mutua: los hilos son exclusivos de los recursos asignados, es decir, un recurso solo puede ser ocupado por un hilo hasta que sea liberado por el hilo.
  2. Solicitud y condiciones de retención: cuando un hilo se bloquea debido a que un recurso está ocupado por la solicitud, retendrá los recursos obtenidos.
  3. Condición de no privación: los recursos obtenidos por el subproceso no pueden ser privados por la fuerza por otros subprocesos antes de que se agoten, y solo se pueden liberar después de que se hayan agotado por sí mismos.
  4. Condición de espera circular: cuando se produce un interbloqueo, el subproceso que espera el bloqueo debe formar un bucle infinito

Formas de evitar interbloqueos

Solo se debe violar una de las cuatro condiciones anteriores:

  • Destrucción de las condiciones de exclusión mutua: debido a que las condiciones de exclusión mutua son una característica del propio bloqueo, no se pueden destruir
  • Destruya las condiciones de solicitud y espera: encapsule el recurso que se va a solicitar en una clase y luego bloquee su objeto
  • Condiciones de destrucción y no privación: se puede establecer que cuando algunos recursos están ocupados y se descubre que no pueden solicitar otros recursos, los recursos que se han solicitado se liberan activamente y luego se vuelven a aplicar.
  • Destruya la condición de espera circular: solicite recursos en un orden determinado y libere recursos en orden inverso

método específico:

  • Intente usar el método de tryLock (tiempo de espera largo, unidad de unidad de tiempo) (ReentrantLock, ReentrantReadWriteLock), configure el tiempo de espera para que salga después del tiempo de espera para evitar un punto muerto
  • Intente usar clases concurrentes java.util.concurrent en lugar de bloqueos escritos a mano
  • Intente reducir la granularidad del uso del bloqueo y no use el mismo bloqueo para varias funciones.

La diferencia entre interbloqueo y livelock

Interbloqueo: se refiere a un bucle infinito causado por dos o más subprocesos que retienen los recursos del otro y no los liberan activamente durante la ejecución.

Livelock: Significa que el hilo no está bloqueado, pero debido a que no se cumplen algunas condiciones, el hilo sigue repitiendo el proceso de adquirir el bloqueo.

Diferencia: el estado del subproceso en el interbloqueo cambia constantemente, pero el estado del subproceso en el interbloqueo no ha cambiado y está en un estado de espera. El interbloqueo puede liberarse por sí mismo, pero el interbloqueo no puede

Lo anterior es un resumen de los puntos de conocimiento de la seguridad de subprocesos múltiples. Con la profundización del aprendizaje de seguimiento, el contenido se complementará y modificará sincrónicamente. Será un gran honor ayudar a todos los bloggers. Por favor, corríjame

Supongo que te gusta

Origin blog.csdn.net/m0_46233999/article/details/118853323
Recomendado
Clasificación