Esempio di deadlock in Java

Il deadlock in Java è una situazione di programmazione in cui due o più thread sono bloccati per sempre. La situazione di deadlock in Java si verifica con almeno due thread e due o più risorse. Qui ho scritto un semplice programma che causerà uno scenario di deadlock in Java e poi vedremo come analizzarlo.

Deadlock in Java

Diamo un’occhiata a un programma semplice in cui creerò un deadlock nei thread Java.

package com.journaldev.threads;

public class ThreadDeadlock {

    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        Object obj2 = new Object();
        Object obj3 = new Object();
    
        Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
        Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
        Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
        
        t1.start();
        Thread.sleep(5000);
        t2.start();
        Thread.sleep(5000);
        t3.start();
        
    }

}

class SyncThread implements Runnable{
    private Object obj1;
    private Object obj2;

    public SyncThread(Object o1, Object o2){
        this.obj1=o1;
        this.obj2=o2;
    }
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name + " acquiring lock on "+obj1);
        synchronized (obj1) {
         System.out.println(name + " acquired lock on "+obj1);
         work();
         System.out.println(name + " acquiring lock on "+obj2);
         synchronized (obj2) {
            System.out.println(name + " acquired lock on "+obj2);
            work();
        }
         System.out.println(name + " released lock on "+obj2);
        }
        System.out.println(name + " released lock on "+obj1);
        System.out.println(name + " finished execution.");
    }
    private void work() {
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Nel programma sopra, SyncThread sta implementando l’interfaccia Runnable e lavora su due oggetti acquisendo il blocco su ognuno di essi uno alla volta utilizzando un blocco sincronizzato. Nel metodo principale, ho tre thread in esecuzione per SyncThread e c’è una risorsa condivisa tra ciascuno dei thread. I thread vengono eseguiti in modo tale che possano acquisire il blocco sul primo oggetto ma quando cercano di acquisire il blocco sul secondo oggetto, vanno nello stato di attesa perché è già bloccato da un altro thread. Questo forma una dipendenza ciclica per la risorsa tra i thread causando il deadlock. Quando eseguo il programma sopra, ecco l’output generato ma il programma non termina mai a causa del deadlock nei thread Java.

t1 acquiring lock on java.lang.Object@6d9dd520
t1 acquired lock on java.lang.Object@6d9dd520
t2 acquiring lock on java.lang.Object@22aed3a5
t2 acquired lock on java.lang.Object@22aed3a5
t3 acquiring lock on java.lang.Object@218c2661
t3 acquired lock on java.lang.Object@218c2661
t1 acquiring lock on java.lang.Object@22aed3a5
t2 acquiring lock on java.lang.Object@218c2661
t3 acquiring lock on java.lang.Object@6d9dd520

Qui possiamo identificare chiaramente la situazione di deadlock dall’output ma nelle applicazioni della vita reale è molto difficile trovare la situazione di deadlock e debuggarla.

Come individuare un deadlock in Java

Per individuare un deadlock in Java, è necessario esaminare il dump dei thread Java dell’applicazione. Nel post precedente ho spiegato come generare un dump dei thread utilizzando il profiler VisualVM o utilizzando l’utilità jstack. Ecco il dump dei thread del programma sopra menzionato.

2012-12-27 19:08:34
Full thread dump Java HotSpot(TM) 64-Bit Server VM (23.5-b02 mixed mode):

"Attach Listener" daemon prio=5 tid=0x00007fb0a2814000 nid=0x4007 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" prio=5 tid=0x00007fb0a2801000 nid=0x1703 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"t3" prio=5 tid=0x00007fb0a204b000 nid=0x4d07 waiting for monitor entry [0x000000015d971000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f658> (a java.lang.Object)
	- locked <0x000000013df2f678> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)

"t2" prio=5 tid=0x00007fb0a1073000 nid=0x4207 waiting for monitor entry [0x000000015d209000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f678> (a java.lang.Object)
	- locked <0x000000013df2f668> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)

"t1" prio=5 tid=0x00007fb0a1072000 nid=0x5503 waiting for monitor entry [0x000000015d86e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f668> (a java.lang.Object)
	- locked <0x000000013df2f658> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)

"Service Thread" daemon prio=5 tid=0x00007fb0a1038000 nid=0x5303 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" daemon prio=5 tid=0x00007fb0a1037000 nid=0x5203 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" daemon prio=5 tid=0x00007fb0a1016000 nid=0x5103 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=5 tid=0x00007fb0a4003000 nid=0x5003 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=5 tid=0x00007fb0a4800000 nid=0x3f03 in Object.wait() [0x000000015d0c0000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
	- locked <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)

"Reference Handler" daemon prio=5 tid=0x00007fb0a4002000 nid=0x3e03 in Object.wait() [0x000000015cfbd000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000013de75320> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:503)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
	- locked <0x000000013de75320> (a java.lang.ref.Reference$Lock)

"VM Thread" prio=5 tid=0x00007fb0a2049800 nid=0x3d03 runnable 

"GC task thread#0 (ParallelGC)" prio=5 tid=0x00007fb0a300d800 nid=0x3503 runnable 

"GC task thread#1 (ParallelGC)" prio=5 tid=0x00007fb0a2001800 nid=0x3603 runnable 

"GC task thread#2 (ParallelGC)" prio=5 tid=0x00007fb0a2003800 nid=0x3703 runnable 

"GC task thread#3 (ParallelGC)" prio=5 tid=0x00007fb0a2004000 nid=0x3803 runnable 

"GC task thread#4 (ParallelGC)" prio=5 tid=0x00007fb0a2005000 nid=0x3903 runnable 

"GC task thread#5 (ParallelGC)" prio=5 tid=0x00007fb0a2005800 nid=0x3a03 runnable 

"GC task thread#6 (ParallelGC)" prio=5 tid=0x00007fb0a2006000 nid=0x3b03 runnable 

"GC task thread#7 (ParallelGC)" prio=5 tid=0x00007fb0a2006800 nid=0x3c03 runnable 

"VM Periodic Task Thread" prio=5 tid=0x00007fb0a1015000 nid=0x5403 waiting on condition 

JNI global references: 114


Found one Java-level deadlock:
=============================
"t3":
  waiting to lock monitor 0x00007fb0a1074b08 (object 0x000000013df2f658, a java.lang.Object),
  which is held by "t1"
"t1":
  waiting to lock monitor 0x00007fb0a1010f08 (object 0x000000013df2f668, a java.lang.Object),
  which is held by "t2"
"t2":
  waiting to lock monitor 0x00007fb0a1012360 (object 0x000000013df2f678, a java.lang.Object),
  which is held by "t3"

Java stack information for the threads listed above:
===================================================
"t3":
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f658> (a java.lang.Object)
	- locked <0x000000013df2f678> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)
"t1":
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f668> (a java.lang.Object)
	- locked <0x000000013df2f658> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)
"t2":
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f678> (a java.lang.Object)
	- locked <0x000000013df2f668> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)

Found 1 deadlock.

L’output del dump dei thread mostra chiaramente la situazione di deadlock e i thread e le risorse coinvolti che causano la situazione di deadlock. Per analizzare il deadlock, è necessario cercare i thread con stato BLOCCATO e quindi le risorse per cui sta aspettando di bloccare. Ogni risorsa ha un ID univoco con cui possiamo scoprire quale thread ha già il blocco sull’oggetto. Ad esempio, il thread “t3” sta aspettando di bloccare 0x000000013df2f658 ma è già bloccato dal thread “t1”. Una volta analizzata la situazione di deadlock e individuati i thread che causano il deadlock, è necessario apportare modifiche al codice per evitare la situazione di deadlock.

Come evitare il deadlock in Java

Queste sono alcune linee guida che possiamo seguire per evitare la maggior parte delle situazioni di deadlock.

  • Avoidare Lock annidati: Questa è la ragione più comune per i deadlock, evitare di bloccare un’altra risorsa se ne si possiede già una. È quasi impossibile ottenere una situazione di deadlock se si lavora con un solo blocco di oggetti. Ad esempio, ecco un’altra implementazione del metodo run() senza blocco annidato e il programma viene eseguito con successo senza situazioni di deadlock.

        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println(name + " acquisizione del blocco su " + obj1);
            synchronized (obj1) {
                System.out.println(name + " ha acquisito il blocco su " + obj1);
                lavoro();
            }
            System.out.println(name + " ha rilasciato il blocco su " + obj1);
            System.out.println(name + " acquisizione del blocco su " + obj2);
            synchronized (obj2) {
                System.out.println(name + " ha acquisito il blocco su " + obj2);
                lavoro();
            }
            System.out.println(name + " ha rilasciato il blocco su " + obj2);
    
            System.out.println(name + " esecuzione completata.");
        }
    
  • Blocca Solo Ciò Che è Necessario: Dovresti acquisire il blocco solo sulle risorse su cui devi lavorare, ad esempio nel programma sopra sto bloccando l’intera risorsa dell’oggetto ma se siamo interessati solo a uno dei suoi campi, allora dovremmo bloccare solo quel campo specifico e non l’intero oggetto.

  • Evitare di Attendere Indefinitamente: Puoi incorrere in un deadlock se due thread stanno aspettando reciprocamente di finire indefinitamente usando join del thread. Se il tuo thread deve attendere che un altro thread finisca, è sempre meglio utilizzare join con il tempo massimo per cui desideri attendere che il thread finisca.

Questo è tutto per i deadlock nei thread Java.

Source:
https://www.digitalocean.com/community/tutorials/deadlock-in-java-example