DodeLock in Java Voorbeeld

Deadlock in Java is een programmeersituatie waarin twee of meer threads voor altijd geblokkeerd zijn. Een Java deadlock situatie ontstaat met minstens twee threads en twee of meer resources. Hier heb ik een eenvoudig programma geschreven dat een Java deadlock scenario veroorzaakt, en dan zullen we zien hoe we dit kunnen analyseren.

Deadlock in Java

Laten we eens kijken naar een eenvoudig programma waarin ik een deadlock creëer in Java threads.

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();
        }
    }
}

In het bovenstaande programma implementeert SyncThread de Runnable interface en werkt het op twee objecten door lock op elk ervan één voor één te verkrijgen met behulp van een gesynchroniseerd blok. In de main methode worden drie threads uitgevoerd voor SyncThread en is er een gedeelde resource tussen elk van de threads. De threads worden zo uitgevoerd dat ze lock kunnen verkrijgen op het eerste object, maar wanneer ze proberen lock te verkrijgen op het tweede object, gaan ze in de wachtstand omdat het al vergrendeld is door een andere thread. Dit veroorzaakt een cyclische afhankelijkheid van de resource tussen threads, wat deadlock veroorzaakt. Wanneer ik het bovenstaande programma uitvoer, wordt de volgende output gegenereerd, maar het programma wordt nooit beëindigd vanwege deadlock in Java threads.

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

Hier kunnen we duidelijk de deadlock situatie identificeren aan de hand van de output, maar in real-life toepassingen is het erg moeilijk om de deadlock situatie te vinden en te debuggen.

Hoe Deadlock Detecteren in Java

Om een deadlock in Java te detecteren, moeten we kijken naar de Java thread dump van de toepassing. In mijn vorige bericht heb ik uitgelegd hoe we een thread dump kunnen genereren met behulp van de VisualVM-profiler of de jstack-hulpprogramma. Hier is de thread dump van het bovenstaande programma.

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.

De thread dump-output toont duidelijk de situatie van deadlock en de threads en middelen die betrokken zijn bij de deadlock-situatie. Bij het analyseren van een deadlock moeten we letten op de threads met de status BLOCKED en vervolgens de middelen waarop het wacht om te vergrendelen waiting to lock. Elk middel heeft een unieke ID waarmee we kunnen achterhalen welke thread al de vergrendeling op het object heeft. Bijvoorbeeld Thread “t3” wacht om 0x000000013df2f658 te vergrendelen, maar het is al vergrendeld door thread “t1”. Nadat we de situatie van deadlock hebben geanalyseerd en de threads hebben gevonden die de deadlock veroorzaken, moeten we wijzigingen aanbrengen in de code om de deadlock-situatie te vermijden.

Hoe Deadlock te Voorkomen in Java

Dit zijn enkele richtlijnen waarmee we de meeste situaties van deadlock kunnen vermijden.

  • Vermijd Geneste Vergrendelingen: Dit is de meest voorkomende reden voor deadlocks, vermijd het vergrendelen van een ander resource als je er al een vasthoudt. Het is bijna onmogelijk om in een deadlock-situatie te belanden als je slechts één object vergrendelt. Hier is bijvoorbeeld een andere implementatie van de run()-methode zonder geneste vergrendeling, en het programma draait succesvol zonder deadlock-situatie.

        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println(name + " vergrendelt " + obj1);
            synchronized (obj1) {
                System.out.println(name + " heeft vergrendeling op " + obj1);
                werk();
            }
            System.out.println(name + " ontgrendelt " + obj1);
            System.out.println(name + " vergrendelt " + obj2);
            synchronized (obj2) {
                System.out.println(name + " heeft vergrendeling op " + obj2);
                werk();
            }
            System.out.println(name + " ontgrendelt " + obj2);
    
            System.out.println(name + " heeft de uitvoering voltooid.");
        }
    
  • Vergrendel alleen wat nodig is: Je moet alleen vergrendelen op de resources waarmee je moet werken. Bijvoorbeeld, in het bovenstaande programma vergrendel ik de volledige objectresource, maar als we alleen geïnteresseerd zijn in een van de velden, dan moeten we alleen dat specifieke veld vergrendelen, niet het volledige object.

  • Vermijd oneindig wachten: Je kunt een deadlock krijgen als twee threads oneindig op elkaar wachten om te eindigen, gebruikmakend van thread join. Als je thread moet wachten op een andere thread om te eindigen, is het altijd het beste om join te gebruiken met de maximale tijd die je wilt wachten totdat de thread is afgelopen.

Dat is alles over deadlock in Java-threads.

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