Un deadlock en Java est une situation de programmation où deux ou plusieurs threads sont bloqués indéfiniment. Une situation de deadlock en Java se produit avec au moins deux threads et deux ressources ou plus. Voici un programme simple qui entraînera un scénario de deadlock en Java, puis nous verrons comment l’analyser.
Deadlock en Java
Jetons un coup d’œil à un programme simple où je vais créer un deadlock dans les threads 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();
}
}
}
Dans le programme ci-dessus, SyncThread implémente l’interface Runnable et travaille sur deux objets en acquérant le verrou sur chacun d’eux un par un à l’aide du bloc synchronized. Dans la méthode principale, j’ai trois threads en cours d’exécution pour SyncThread et il y a une ressource partagée entre chacun des threads. Les threads sont exécutés de manière à pouvoir acquérir le verrou sur le premier objet, mais lorsqu’ils essaient d’acquérir le verrou sur le deuxième objet, ils passent à l’état d’attente car il est déjà verrouillé par un autre thread. Cela crée une dépendance cyclique pour la ressource entre les threads, provoquant un deadlock. Lorsque j’exécute le programme ci-dessus, voici la sortie générée, mais le programme ne se termine jamais en raison du deadlock dans les threads 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
Ici, nous pouvons clairement identifier la situation de deadlock à partir de la sortie, mais dans les applications de la vie réelle, il est très difficile de trouver la situation de deadlock et de les déboguer.
Comment détecter un deadlock en Java
Pour détecter un deadlock en Java, nous devons examiner le java thread dump de l’application. Dans le dernier message, j’ai expliqué comment générer un thread dump à l’aide du profileur VisualVM ou de l’utilitaire jstack
. Voici le thread dump du programme ci-dessus.
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.
La sortie du thread dump montre clairement la situation de deadlock et les threads ainsi que les ressources impliquées qui provoquent la situation de deadlock. Pour analyser le deadlock, nous devons rechercher les threads dont l’état est BLOCKED et ensuite les ressources sur lesquelles ils attendent de verrouiller. Chaque ressource a un identifiant unique que nous pouvons utiliser pour trouver le thread qui détient déjà le verrou sur l’objet. Par exemple, le thread « t3 » attend de verrouiller 0x000000013df2f658, mais il est déjà verrouillé par le thread « t1 ». Une fois que nous avons analysé la situation de deadlock et identifié les threads responsables du deadlock, nous devons apporter des modifications au code pour éviter la situation de deadlock.
Comment éviter un deadlock en Java
Voici quelques directives que nous pouvons suivre pour éviter la plupart des situations de deadlock.
-
Évitez les verrous imbriqués: C’est la raison la plus courante des deadlocks, évitez de verrouiller une autre ressource si vous en détenez déjà une. Il est presque impossible de se retrouver dans une situation de deadlock si vous travaillez avec un seul verrou d’objet. Par exemple, voici une autre implémentation de la méthode run() sans verrouillage imbriqué et le programme s’exécute avec succès sans situation de deadlock.
public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " acquiert le verrou sur " + obj1); synchronized (obj1) { System.out.println(name + " a acquis le verrou sur " + obj1); work(); } System.out.println(name + " a libéré le verrou sur " + obj1); System.out.println(name + " acquiert le verrou sur " + obj2); synchronized (obj2) { System.out.println(name + " a acquis le verrou sur " + obj2); work(); } System.out.println(name + " a libéré le verrou sur " + obj2); System.out.println(name + " a terminé l'exécution."); }
-
Ne verrouillez que ce qui est nécessaire: Vous devez acquérir un verrou uniquement sur les ressources sur lesquelles vous devez travailler. Par exemple, dans le programme ci-dessus, je verrouille la ressource complète de l’objet, mais si nous ne sommes intéressés que par l’un de ses champs, nous devrions verrouiller uniquement ce champ spécifique, pas l’objet complet.
-
Évitez d’attendre indéfiniment: Vous pouvez rencontrer un verrouillage mutuel si deux threads attendent indéfiniment que l’autre se termine en utilisant thread join. Si votre thread doit attendre qu’un autre thread se termine, il est toujours préférable d’utiliser join avec le temps maximum que vous souhaitez attendre pour que le thread se termine.
Voilà tout pour les deadlocks dans les threads Java.
Source:
https://www.digitalocean.com/community/tutorials/deadlock-in-java-example