¿Qué es un punto muerto? ¿Entiendes las instrucciones del código de bytes del punto muerto?

Utilice un lenguaje humorístico y sencillo para describir el punto muerto.

Bansheng: Ya obtuve el primer lugar en la prueba por computadora y solo me falta el primer lugar en la prueba escrita.

Primaria Uno: Ya gané el primer lugar en la prueba escrita y solo me falta el primer lugar en la prueba por computadora.

Entrevistador: Soy muy optimista acerca de ustedes dos, sigan trabajando, solo si obtienen el primer lugar entre los dos al mismo tiempo podrán recibir una oferta y ingresar a mi gran fábrica XX.

Ban Sheng: Junior One, ocupaste el primer lugar en la prueba escrita, ¿qué tal si me dejas hacerlo?

Xiaoyi: Soñando, ¡quiero tu primer lugar en la prueba por computadora!

Ban Sheng: Está a tu nivel, zz

Xiaoyi: WTF, peleemos

Ban Sheng: Vamos, ¿quién tiene miedo de quién?

Entonces: se produce un punto muerto, los valientes ganan cuando se encuentran en un camino estrecho y la computadora falla.

¿Qué es un punto muerto? ¿Cuáles son las condiciones para que se produzca un punto muerto?

1. Un punto muerto se refiere a una situación en la que dos o más subprocesos esperan entre sí para liberar los recursos que tienen entre sí, lo que hace que el proceso no pueda continuar ejecutándose. Específicamente, cuando se produce un punto muerto, el subproceso entrará en un estado de espera permanente, incapaz de continuar con la ejecución y, finalmente, provocará que el programa deje de responder o se bloquee.

2. Las condiciones para generar interbloqueos, comúnmente conocidas como las cuatro condiciones necesarias para interbloqueos, incluyen:

  1. Exclusión mutua: al menos un recurso está ocupado exclusivamente por varios subprocesos, lo que significa que un recurso solo puede estar ocupado por un subproceso al mismo tiempo.
  2. Condiciones de solicitud y retención (Hold and Wait): el subproceso ya posee al menos un recurso y está esperando adquirir recursos ocupados por otros subprocesos.
  3. Sin condición de preferencia (Sin preferencia): el recurso obtenido no puede ser reemplazado por otros subprocesos y solo puede ser liberado explícitamente por el subproceso que lo posee.
  4. Condición de espera circular (espera circular): varios subprocesos forman una cadena de espera circular, es decir, cada subproceso está esperando el recurso retenido por el siguiente subproceso.

El punto muerto sólo puede ocurrir cuando las cuatro condiciones anteriores se cumplen simultáneamente.

El siguiente fragmento de código (código con problemas, prueba tu vista y habilidades básicas)

 public static void main(String[] args) {
        final Object resource1 = new Object();
        final Object resource2 = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 1:锁住offer1");
            }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource2) {
                    System.out.println("Thread 1:锁住offer2");
                }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println("Thread 2:锁住offer2");
            }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource1) {
                    System.out.println("Thread 2:锁住offer1");
                }
        });

        thread1.start();
        thread2.start();
    }

¿Por qué estos dos hilos no pueden bloquear esta oferta?

7c7be128d11d4408a42624aab68a8cfe.png

El agua gorda no fluye hacia los forasteros, ya que ninguno de ustedes puede bloquear esta oferta, déjenme ir ~

public static void main(String[] args) {
        final Object resource1 = new Object();
        final Object resource2 = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("半生:锁住了机考第一名");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource2) {
                    System.out.println("半生:笔试第一名");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println("小一:锁住了笔试第一名");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource1) {
                    System.out.println("小一:锁住机考第一名");
                }
            }
        });

        thread1.start();
        thread2.start();
    }

6187c605ae9847b1b2d39a5eccaab3af.png

Son ustedes dos otra vez, ¿qué están haciendo? Está estancado ~ Si tienen más de una oferta, no pueden llevarse bien, divertirse y recibir ofertas al mismo tiempo.

Déjame ver cómo se ejecutan las instrucciones del código de bytes.

1. Primero use javac -encoding UTF-8 X.java para generar archivos de clase

2. descompilación javap -verbose X.class

3. A juzgar por las instrucciones descompiladas, debería ser que el sistema operativo o la máquina virtual JVM ha detectado un punto muerto y forzó una interrupción. Cuando usamos sincronizado como bloqueo, sabemos que hay un par de instrucciones monitorenter monitorexit. Pero yo no lo vi aquí

4. Descargue un complemento de ideas jclasslib para verificar si las instrucciones del código de bytes son consistentes.

5. Puede ver que hay un par de instrucciones de bloqueo aquí. Aquí hay una breve explicación del significado de las instrucciones del código de bytes de escritura superior.

0 aload_0:加载索引为0的引用到操作数栈,通常用于加载实例方法的隐式参数,即this。

1 dup:复制栈顶的元素,并将复制后的值重新压入栈顶。

2 astore_2:将栈顶的引用类型数值存储到局部变量表的索引为2的位置。

3 monitorenter:进入同步块前获取锁。

4 getstatic #3 <java/lang/System.out : Ljava/io/PrintStream;>:获取静态字段System.out的值,即标准输出流PrintStream对象。

7 ldc #12 <小一:锁住了笔试第一名>:将常量池中索引为12的String类型常量加载到操作数栈。

9 invokevirtual #5 <java/io/PrintStream.println : (Ljava/lang/String;)V>:执行PrintStream对象的println方法,其中参数为栈顶的String类型常量。

12 ldc2_w #13 <1000>:将常量池中索引为13的long类型常量加载到操作数栈。

15 invokestatic #15 <java/lang/Thread.sleep : (J)V>:执行Thread类的静态方法sleep,其中参数为栈顶的long类型常量。

18 goto 33 (+15):无条件跳转到字节码指令33,即跳过下方的指令。

21 astore_3:将栈顶的引用类型数值存储到局部变量表的索引为3的位置。

22 getstatic #3 <java/lang/System.out : Ljava/io/PrintStream;>:获取静态字段System.out的值。

25 ldc #17 <小一:被中断,释放笔试资源>:将常量池中索引为17的String类型常量加载到操作数栈。

27 invokevirtual #5 <java/io/PrintStream.println : (Ljava/lang/String;)V>:执行PrintStream对象的println方法,其中参数为栈顶的String类型常量。

30 aload_2:加载局部变量表中索引为2的引用类型数值到操作数栈。

31 monitorexit:退出同步块,释放锁。

32 return:返回void类型的值,并结束当前方法。

33 aload_1:加载局部变量表中索引为1的引用类型数值到操作数栈。

34 dup:复制栈顶的元素,并将复制后的值重新压入栈顶。

35 astore_3:将栈顶的引用类型数值存储到局部变量表的索引为3的位置。

instrucciones aquí

No. 9 invokevirtual #5 <java/io/PrintStream.println: (Ljava/lang/String;)V>: Ejecute el método println del objeto PrintStream, donde el parámetro es la constante de tipo String en la parte superior de la pila. Se ejecuta el registro de impresión en la captura, lo que indica que la ejecución fue interrumpida, y luego ir a 33 salta a la instrucción en la línea 33.

6. Además, escribí otro método de sincronización, echemos un vistazo.

Se puede ver desde aquí que está bloqueado. Hay una salida de monitor adicional en la línea 20. Esto es para evitar la liberación forzada anormal del bloqueo, que es la garantía de que sincronizado pueda liberar el bloqueo automáticamente.

Entonces: aquí viene la manera de resolver el punto muerto ~

Da un paso atrás y piensa en el cielo, hola a mí, hola a todos.

public static void main(String[] args) {
        final Object resource1 = new Object();
        final Object resource2 = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("半生:获得了机考第一名");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource2) {
                    System.out.println("半生:获得了笔试第一名");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("小一:获得了笔试第一名");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource2) {
                    System.out.println("小一:获得了机考第一名");
                }
            }
        });

        thread1.start();
        thread2.start();
    }

85cc84572eba4e8b85376390faadb889.png

Siempre que cambié ligeramente el orden de obtención de recursos, Bansheng y Primaria 1 aprobaron la prueba por computadora, ocuparon el primer lugar en la prueba escrita y ambos recibieron ofertas de XX fabricantes.

Hay muchas formas de salir del punto muerto, siempre que se elimine una de las cuatro condiciones necesarias para el punto muerto.

Los siguientes métodos comunes se utilizan habitualmente para resolver problemas de interbloqueo :

  1. Evite la espera circular: al estipular el orden en el que se bloquean los recursos, se evita que los subprocesos esperen los recursos de los demás. Puede acordar el orden de adquisición de recursos mediante clasificación o numeración para evitar esperas circulares.

  2. Destruir solicitudes y condiciones de retención: permite que un subproceso obtenga todos los recursos necesarios a la vez cuando solicita recursos, o libera recursos ya ocupados cuando obtiene un determinado recurso. Esto evita situaciones en las que un subproceso retiene un recurso mientras espera que se libere otro recurso.

  3. Utilice la privación de recursos: cuando un subproceso solicita un recurso, si el recurso ya está ocupado por otros subprocesos, puede privar temporalmente a otros subprocesos del bloqueo del recurso para satisfacer las necesidades del subproceso actual. El hilo privado puede esperar un período de tiempo antes de volver a solicitar recursos.

  4. Utilice un mecanismo de tiempo de espera: establezca un tiempo de espera al adquirir un recurso de bloqueo. Si el recurso no se puede obtener dentro del tiempo especificado, abandone la adquisición, libere el recurso ocupado y luego vuelva a intentarlo.

  5. Detección y recuperación de interbloqueos: al detectar situaciones de interbloqueo en el sistema, los subprocesos estancados se pueden recuperar o finalizar. Los algoritmos comunes de detección de interbloqueos incluyen el algoritmo del gráfico de asignación de recursos y el algoritmo bancario.

Cabe señalar que diferentes soluciones son adecuadas para diferentes escenarios y problemas, y la elección del método apropiado debe evaluarse en función de la situación específica. Además, es una mejor práctica evitar problemas de interbloqueo. Durante el diseño y la implementación, trate de evitar condiciones que puedan provocar un punto muerto y, fundamentalmente, elimine la aparición de problemas de punto muerto.

Supongo que te gusta

Origin blog.csdn.net/weixin_42450130/article/details/132672163
Recomendado
Clasificación