Totenpunkt in Java ist eine Programmiersituation, in der zwei oder mehr Threads für immer blockiert sind. Die Java-Deadlock-Situation entsteht mit mindestens zwei Threads und zwei oder mehr Ressourcen. Hier habe ich ein einfaches Programm geschrieben, das eine Java-Deadlock-Situation verursachen wird, und dann werden wir sehen, wie man sie analysieren kann.
Deadlock in Java
Werfen wir einen Blick auf ein einfaches Programm, in dem ich eine Deadlock-Situation in Java-Threads erstellen werde.
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();
}
}
}
Im obigen Programm implementiert SyncThread das Runnable-Interface und arbeitet mit zwei Objekten, indem es nacheinander einen Sperrmechanismus auf jedes davon anwendet. In der Hauptmethode habe ich drei Threads, die für SyncThread ausgeführt werden, und es gibt eine gemeinsame Ressource zwischen jedem der Threads. Die Threads werden so ausgeführt, dass sie das Sperren des ersten Objekts erreichen können. Wenn sie jedoch versuchen, das Sperren des zweiten Objekts zu erreichen, geht es in den Wartezustand über, weil es bereits von einem anderen Thread gesperrt ist. Dies bildet eine zyklische Abhängigkeit für die Ressource zwischen den Threads und verursacht einen Deadlock. Wenn ich das obige Programm ausführe, wird die folgende Ausgabe generiert, aber das Programm endet nie aufgrund des Deadlocks in den 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 können wir die Deadlock-Situation aus der Ausgabe klar identifizieren, aber in Anwendungen des wirklichen Lebens ist es sehr schwierig, die Deadlock-Situation zu finden und zu debuggen.
Wie man Deadlocks in Java erkennt
Um einen Deadlock in Java zu erkennen, müssen wir uns den Java-Thread-Dump der Anwendung ansehen. Im letzten Beitrag habe ich erklärt, wie wir mithilfe des VisualVM-Profiling-Tools oder des jstack
-Dienstprogramms einen Thread-Dump generieren können. Hier ist der Thread-Dump des obigen Programms.
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.
Der Thread-Dump-Output zeigt deutlich die Deadlock-Situation und die beteiligten Threads und Ressourcen, die die Deadlock-Situation verursachen. Um den Deadlock zu analysieren, müssen wir nach den Threads mit dem Status BLOCKED suchen und dann nach den Ressourcen, auf die sie warten, um sie zu sperren. Jede Ressource hat eine eindeutige ID, anhand derer wir herausfinden können, welcher Thread bereits das Schloss auf dem Objekt hält. Zum Beispiel wartet Thread „t3“ darauf, 0x000000013df2f658 zu sperren, aber es wird bereits von Thread „t1“ gesperrt. Sobald wir die Deadlock-Situation analysiert und die Threads gefunden haben, die den Deadlock verursachen, müssen wir Code-Änderungen vornehmen, um den Deadlock zu vermeiden.
Wie man Deadlocks in Java vermeidet
Dies sind einige Richtlinien, mit denen wir die meisten Deadlock-Situationen vermeiden können.
-
Vermeiden Sie verschachtelte Sperren: Dies ist der häufigste Grund für Deadlocks. Vermeiden Sie das Sperren einer anderen Ressource, wenn Sie bereits eine besitzen. Es ist nahezu unmöglich, in einer Deadlocksituation zu landen, wenn Sie nur ein Objektschloss verwenden. Hier ist beispielsweise eine weitere Implementierung der Methode run() ohne verschachtelte Sperre, und das Programm läuft erfolgreich ohne Deadlocksituation.
public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " Sperrung von " + obj1); synchronized (obj1) { System.out.println(name + " hat das Schloss für " + obj1 + " erhalten"); work(); } System.out.println(name + " hat das Schloss für " + obj1 + " freigegeben"); System.out.println(name + " Sperrung von " + obj2); synchronized (obj2) { System.out.println(name + " hat das Schloss für " + obj2 + " erhalten"); work(); } System.out.println(name + " hat das Schloss für " + obj2 + " freigegeben"); System.out.println(name + " hat die Ausführung abgeschlossen."); }
-
Sperren Sie nur, was erforderlich ist: Sie sollten das Sperren nur auf die Ressourcen anwenden, die Sie bearbeiten müssen. Zum Beispiel sperre ich im obigen Programm die gesamte Objektressource. Wenn wir jedoch nur an einem seiner Felder interessiert sind, sollten wir nur dieses spezifische Feld sperren, nicht das gesamte Objekt.
-
Vermeiden Sie endloses Warten: Sie können eine Blockade bekommen, wenn zwei Threads unendlich auf das Ende des anderen warten, indem sie \texttt{thread join} verwenden. Wenn Ihr Thread auf das Ende eines anderen Threads warten muss, ist es immer am besten, \texttt{join} mit der maximalen Zeit zu verwenden, die Sie auf das Ende des Threads warten möchten.
Das ist alles zu Deadlocks in Java-Threads.
Source:
https://www.digitalocean.com/community/tutorials/deadlock-in-java-example