Interbloqueo en subprocesos múltiples de Java

La palabra clave sincronizada se usa para hacer que la clase o el método sea seguro para subprocesos, lo que significa que solo un subproceso puede tener el bloqueo del método sincronizado y usarlo, otros subprocesos deben esperar hasta que se libere el bloqueo y cualquiera de ellos adquiera ese bloqueo. 
Es importante usarlo si nuestro programa se ejecuta en un entorno de subprocesos múltiples donde dos o más subprocesos se ejecutan simultáneamente. Pero a veces también causa un problema que se llama Deadlock . A continuación se muestra un ejemplo simple de condición de interbloqueo. 
 

Java

// Java program to illustrate Deadlock
// in multithreading.
class Util
{
    // Util class to sleep a thread
    static void sleep(long millis)
    {
        try
        {
            Thread.sleep(millis);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}
 
// This class is shared by both threads
class Shared
{
    // first synchronized method
    synchronized void test1(Shared s2)
    {
        System.out.println("test1-begin");
        Util.sleep(1000);
 
        // taking object lock of s2 enters
        // into test2 method
        s2.test2();
        System.out.println("test1-end");
    }
 
    // second synchronized method
    synchronized void test2()
    {
        System.out.println("test2-begin");
        Util.sleep(1000);
        // taking object lock of s1 enters
        // into test1 method
        System.out.println("test2-end");
    }
}
 
 
class Thread1 extends Thread
{
    private Shared s1;
    private Shared s2;
 
    // constructor to initialize fields
    public Thread1(Shared s1, Shared s2)
    {
        this.s1 = s1;
        this.s2 = s2;
    }
 
    // run method to start a thread
    @Override
    public void run()
    {
        // taking object lock of s1 enters
        // into test1 method
        s1.test1(s2);
    }
}
 
 
class Thread2 extends Thread
{
    private Shared s1;
    private Shared s2;
 
    // constructor to initialize fields
    public Thread2(Shared s1, Shared s2)
    {
        this.s1 = s1;
        this.s2 = s2;
    }
 
    // run method to start a thread
    @Override
    public void run()
    {
        // taking object lock of s2
        // enters into test2 method
        s2.test1(s1);
    }
}
 
 
public class Deadlock
{
    public static void main(String[] args)
    {
        // creating one object
        Shared s1 = new Shared();
 
        // creating second object
        Shared s2 = new Shared();
 
        // creating first thread and starting it
        Thread1 t1 = new Thread1(s1, s2);
        t1.start();
 
        // creating second thread and starting it
        Thread2 t2 = new Thread2(s1, s2);
        t2.start();
 
        // sleeping main thread
        Util.sleep(2000);
    }
}
Output : test1-begin
test2-begin

No se recomienda ejecutar el programa anterior con IDE en línea. Podemos copiar el código fuente y ejecutarlo en nuestra máquina local. Podemos ver que se ejecuta por tiempo indefinido, porque los subprocesos están en condición de interbloqueo y no permiten que se ejecute el código. Ahora veamos paso a paso lo que está pasando allí. 
 

  1. El subproceso t1 se inicia y llama al método test1 tomando el bloqueo de objeto de s1.
  2. El subproceso t2 se inicia y llama al método test1 tomando el bloqueo de objeto de s2.
  3. t1 imprime test1-begin y t2 imprime test-2 begin y ambos esperan 1 segundo, para que ambos subprocesos puedan iniciarse si alguno de ellos no lo está.
  4. t1 intenta bloquear el objeto de s2 y llamar al método test2, pero como ya lo adquirió t2, espera hasta que se libere. No liberará el bloqueo de s1 hasta que obtenga el bloqueo de s2.
  5. Lo mismo sucede con t2. Intenta tomar el bloqueo de objeto de s1 y llama al método test1, pero t1 ya lo adquirió, por lo que tiene que esperar hasta que t1 libere el bloqueo. t2 tampoco liberará el bloqueo de s2 hasta que obtenga el bloqueo de s1.
  6. Ahora, ambos subprocesos están en estado de espera, esperando que el otro libere los bloqueos. Ahora hay una carrera alrededor de la condición de quién liberará el candado primero.
  7. Como ninguno de ellos está listo para liberar el bloqueo, esta es la condición Dead Lock.
  8. Cuando ejecute este programa, parecerá que la ejecución está en pausa.

Detectar condición Dead Lock

También podemos detectar interbloqueos ejecutando este programa en cmd. Tenemos que recopilar Thread Dump. El comando para recopilar depende del tipo de sistema operativo. Si usamos Windows y Java 8, el comando es jcmd $PID Thread.print 
Podemos obtener PID ejecutando el comando jps. El volcado de hilo para el programa anterior está a continuación:
 

jcmd 18692 Thread.print
18692:
2020-06-08 19:03:10
Full thread dump OpenJDK 64-Bit Server VM (11.0.4+10-b304.69 mixed mode, sharing):

Threads class SMR info:
_java_thread_list=0x0000017f44b69f20, length=13, elements={
0x0000017f43f77000, 0x0000017f43f79800, 0x0000017f43f90000, 0x0000017f43f91000,
0x0000017f43f95000, 0x0000017f43fa5000, 0x0000017f43fb0800, 0x0000017f43f5b800,
0x0000017f44bc9000, 0x0000017f44afb000, 0x0000017f44bd7800, 0x0000017f44bd8800,
0x0000017f298c9000
}

"Reference Handler" #2 daemon prio=10 os_prio=2 cpu=0.00ms elapsed=57.48s tid=0x0000017f43f77000 nid=0x6050 waiting on condition  [0x0000005f800ff000]
   java.lang.Thread.State: RUNNABLE
        at java.lang.ref.Reference.waitForReferencePendingList(java.base@11.0.4/Native Method)
        at java.lang.ref.Reference.processPendingReferences(java.base@11.0.4/Reference.java:241)
        at java.lang.ref.Reference$ReferenceHandler.run(java.base@11.0.4/Reference.java:213)

"Finalizer" #3 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=57.48s tid=0x0000017f43f79800 nid=0x2824 in Object.wait()  [0x0000005f801fe000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(java.base@11.0.4/Native Method)
        - waiting on  (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:155)
        - waiting to re-lock in wait()  (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:176)
        at java.lang.ref.Finalizer$FinalizerThread.run(java.base@11.0.4/Finalizer.java:170)

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=57.47s tid=0x0000017f43f90000 nid=0x1710 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #5 daemon prio=5 os_prio=2 cpu=31.25ms elapsed=57.47s tid=0x0000017f43f91000 nid=0x4ff4 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 cpu=46.88ms elapsed=57.47s tid=0x0000017f43f95000 nid=0x350c waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"C1 CompilerThread0" #9 daemon prio=9 os_prio=2 cpu=93.75ms elapsed=57.47s tid=0x0000017f43fa5000 nid=0x4900 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"Sweeper thread" #10 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=57.47s tid=0x0000017f43fb0800 nid=0x6120 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Common-Cleaner" #11 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=57.44s tid=0x0000017f43f5b800 nid=0x5a4 in Object.wait()  [0x0000005f807fe000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(java.base@11.0.4/Native Method)
        - waiting on  (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:155)
        - waiting to re-lock in wait()  (a java.lang.ref.ReferenceQueue$Lock)
        at jdk.internal.ref.CleanerImpl.run(java.base@11.0.4/CleanerImpl.java:148)
        at java.lang.Thread.run(java.base@11.0.4/Thread.java:834)
        at jdk.internal.misc.InnocuousThread.run(java.base@11.0.4/InnocuousThread.java:134)

"Monitor Ctrl-Break" #12 daemon prio=5 os_prio=0 cpu=15.63ms elapsed=57.36s tid=0x0000017f44bc9000 nid=0x5954 runnable  [0x0000005f809fe000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(java.base@11.0.4/Native Method)
        at java.net.SocketInputStream.socketRead(java.base@11.0.4/SocketInputStream.java:115)
        at java.net.SocketInputStream.read(java.base@11.0.4/SocketInputStream.java:168)
        at java.net.SocketInputStream.read(java.base@11.0.4/SocketInputStream.java:140)
        at sun.nio.cs.StreamDecoder.readBytes(java.base@11.0.4/StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(java.base@11.0.4/StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(java.base@11.0.4/StreamDecoder.java:178)
        - locked  (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(java.base@11.0.4/InputStreamReader.java:185)
        at java.io.BufferedReader.fill(java.base@11.0.4/BufferedReader.java:161)
        at java.io.BufferedReader.readLine(java.base@11.0.4/BufferedReader.java:326)
        - locked  (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(java.base@11.0.4/BufferedReader.java:392)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Service Thread" #13 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=57.36s tid=0x0000017f44afb000 nid=0x6394 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-0" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=57.35s tid=0x0000017f44bd7800 nid=0x5304 waiting for monitor entry  [0x0000005f80cfe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.company.threads.Shared.test2(Deadlock.java:40)
        - waiting to lock  (a com.company.threads.Shared)
        at com.company.threads.Shared.test1(Deadlock.java:33)
        - locked  (a com.company.threads.Shared)
        at com.company.threads.Thread1.run(Deadlock.java:67)

"Thread-1" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=57.35s tid=0x0000017f44bd8800 nid=0xfa4 waiting for monitor entry  [0x0000005f80dfe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.company.threads.Shared.test2(Deadlock.java:40)
        - waiting to lock  (a com.company.threads.Shared)
        at com.company.threads.Shared.test1(Deadlock.java:33)
        - locked  (a com.company.threads.Shared)
        at com.company.threads.Thread2.run(Deadlock.java:90)

"DestroyJavaVM" #16 prio=5 os_prio=0 cpu=171.88ms elapsed=55.35s tid=0x0000017f298c9000 nid=0x38ec waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"VM Thread" os_prio=2 cpu=0.00ms elapsed=57.49s tid=0x0000017f43f73800 nid=0x52c4 runnable

"GC Thread#0" os_prio=2 cpu=0.00ms elapsed=57.51s tid=0x0000017f298e1000 nid=0x47dc runnable

"G1 Main Marker" os_prio=2 cpu=0.00ms elapsed=57.51s tid=0x0000017f29911000 nid=0x61c4 runnable

"G1 Conc#0" os_prio=2 cpu=0.00ms elapsed=57.51s tid=0x0000017f29912000 nid=0x61c0 runnable

"G1 Refine#0" os_prio=2 cpu=0.00ms elapsed=57.50s tid=0x0000017f43e0a800 nid=0x1fa8 runnable

"G1 Young RemSet Sampling" os_prio=2 cpu=0.00ms elapsed=57.50s tid=0x0000017f43e0b000 nid=0x47a4 runnable
"VM Periodic Task Thread" os_prio=2 cpu=0.00ms elapsed=57.36s tid=0x0000017f44b03800 nid=0x2408 waiting on condition

JNI global refs: 15, weak refs: 0


Found one Java-level deadlock:
=============================
"Thread-0":
  waiting to lock monitor 0x0000017f43f87980 (object 0x000000008a2e9ce0, a com.company.threads.Shared),
  which is held by "Thread-1"
"Thread-1":
  waiting to lock monitor 0x0000017f43f87780 (object 0x000000008a2e9cd0, a com.company.threads.Shared),
  which is held by "Thread-0"

Java stack information for the threads listed above:
===================================================
"Thread-0":
        at com.company.threads.Shared.test2(Deadlock.java:40)
        - waiting to lock  (a com.company.threads.Shared)
        at com.company.threads.Shared.test1(Deadlock.java:33)
        - locked  (a com.company.threads.Shared)
        at com.company.threads.Thread1.run(Deadlock.java:67)
"Thread-1":
        at com.company.threads.Shared.test2(Deadlock.java:40)
        - waiting to lock  (a com.company.threads.Shared)
        at com.company.threads.Shared.test1(Deadlock.java:33)
        - locked  (a com.company.threads.Shared)
        at com.company.threads.Thread2.run(Deadlock.java:90)

Found 1 deadlock.

Como podemos ver, se menciona claramente que se encontró 1 interbloqueo. Es posible que aparezca el mismo mensaje cuando pruebes en tu máquina.
 

Evitar la condición de Dead Lock

Podemos evitar la condición de interbloqueo conociendo sus posibilidades. Es un proceso muy complejo y no fácil de atrapar. Pero aun así, si lo intentamos, podemos evitar esto. Hay algunos métodos por los cuales podemos evitar esta condición. No podemos eliminar por completo su posibilidad, pero podemos reducirla.
 

  • Evite los bloqueos anidados: esta es la razón principal del bloqueo muerto. Dead Lock ocurre principalmente cuando damos bloqueos a múltiples subprocesos. Evite dar bloqueo a varios hilos si ya le hemos dado a uno.
  • Evite bloqueos innecesarios: debemos bloquear solo aquellos miembros que son necesarios. Tener el bloqueo activado innecesariamente puede conducir a un bloqueo total.
  • Uso de unión de subprocesos: la condición de bloqueo muerto aparece cuando un subproceso está esperando que otro termine. Si se produce esta condición, podemos usar Thread.join con el tiempo máximo que cree que llevará la ejecución.

Puntos importantes : 
 

  • Si los subprocesos están esperando entre sí para terminar, la condición se conoce como interbloqueo.
  • La condición de interbloqueo es una condición compleja que ocurre solo en el caso de múltiples subprocesos.
  • La condición de interbloqueo puede romper nuestro código en tiempo de ejecución y puede destruir la lógica comercial.
  • Debemos evitar esta condición tanto como podamos.

Este artículo es una contribución de Vishal Garg . Si te gusta GeeksforGeeks y te gustaría contribuir, también puedes escribir un artículo usando write.geeksforgeeks.org o enviar tu artículo por correo a review-team@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.
Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.
 

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *