Deadlock em Java é uma situação de programação em que dois ou mais threads estão bloqueados indefinidamente. A situação de deadlock em Java surge com pelo menos dois threads e dois ou mais recursos. Aqui está um programa simples que causará um cenário de deadlock em Java, e então veremos como analisá-lo.
Deadlock em Java
Vamos dar uma olhada em um programa simples onde criarei um deadlock em 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();
}
}
}
No programa acima, SyncThread está implementando a interface Runnable e trabalha com dois objetos, adquirindo bloqueio em cada um deles, um de cada vez, usando um bloco sincronizado. No método principal, tenho três threads em execução para SyncThread, e há um recurso compartilhado entre cada uma delas. As threads são executadas de tal maneira que conseguem adquirir o bloqueio no primeiro objeto, mas ao tentar adquirir o bloqueio no segundo objeto, entram em estado de espera porque já está bloqueado por outra thread. Isso forma uma dependência cíclica para o recurso entre as threads, causando o deadlock. Quando executo o programa acima, aqui está a saída gerada, mas o programa nunca termina devido ao deadlock em 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
Aqui podemos identificar claramente a situação de deadlock a partir da saída, mas em aplicações da vida real, é muito difícil encontrar a situação de deadlock e depurá-las.
Como Detectar Deadlock em Java
Para detectar um deadlock em Java, precisamos examinar o dump de threads Java da aplicação. No último post, expliquei como podemos gerar um dump de threads usando o perfilador VisualVM ou utilizando a utilidade jstack
. Aqui está o dump de threads do programa acima.
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.
A saída do dump de threads mostra claramente a situação de deadlock e as threads e recursos envolvidos que causam a situação de deadlock. Para analisar o deadlock, precisamos observar as threads com estado BLOQUEADO e então os recursos pelos quais ela está aguardando para bloquear. Cada recurso tem um ID único com o qual podemos descobrir qual thread já está mantendo o bloqueio no objeto. Por exemplo, a Thread “t3” está aguardando para bloquear 0x000000013df2f658, mas já está bloqueada pela thread “t1”. Uma vez que analisamos a situação de deadlock e identificamos as threads que estão causando o deadlock, precisamos fazer alterações no código para evitar a situação de deadlock.
Como Evitar Deadlock em Java
Essas são algumas das diretrizes que podemos seguir para evitar a maioria das situações de deadlock.
-
Avoidar Trancas Aninhadas: Esta é a razão mais comum para deadlocks, evite bloquear outro recurso se você já possuir um. É quase impossível entrar em uma situação de deadlock se você estiver trabalhando com apenas um objeto bloqueado. Por exemplo, aqui está outra implementação do método run() sem trancas aninhadas e o programa é executado com sucesso sem situação de deadlock.
public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " adquirindo trava em " + obj1); synchronized (obj1) { System.out.println(name + " adquiriu trava em " + obj1); trabalho(); } System.out.println(name + " liberou trava em " + obj1); System.out.println(name + " adquirindo trava em " + obj2); synchronized (obj2) { System.out.println(name + " adquiriu trava em " + obj2); trabalho(); } System.out.println(name + " liberou trava em " + obj2); System.out.println(name + " terminou a execução."); }
-
Bloquear Apenas o Necessário: Você deve adquirir a trava apenas nos recursos nos quais precisa trabalhar, por exemplo, no programa acima, estou travando o recurso completo do Objeto, mas se estamos interessados apenas em um de seus campos, então devemos travar apenas esse campo específico e não o objeto completo.
-
Avoid waiting indefinitely: Você pode obter um impasse se duas threads estiverem esperando uma pela outra para terminar indefinidamente usando thread join. Se sua thread precisar esperar que outra thread termine, é sempre melhor usar join com o tempo máximo que deseja esperar para a thread terminar.
Isso é tudo para impasses em threads Java.
Source:
https://www.digitalocean.com/community/tutorials/deadlock-in-java-example