3 дек. 2009 г.

JVisualVM: DeadLock detection

По работе забулшитил deadlock, собственно у меня было подозрение, что он возможен... да и просмотреть стоило бы хорошо ещё раз, но всё же он вылез чуть раньше на unit test'ах, чем я нашёл его в результате code review.

Для демонстрации поиска deadlock используем классическую ситуацию с переводом средств с одного счёта на другой:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class DeadLock {

public static class Account {
public int id;
public long sum;

public Account(int id, long sum) {
this.id = id;
this.sum = sum;
}
}

public static void trasfer(final Account a1, final Account a2,
final long sum) {
synchronized (a1) {
try {
// чуть подождём, чтобы наверняка
Thread.sleep(50L);
} catch (InterruptedException e) {
//
}
synchronized (a2) {
if (a1.sum >= sum) {
System.out.println(Thread.currentThread().getName() + ": "
+ a1.id + " -> " + a2.id + " sum:" + sum);
a1.sum -= sum;
a2.sum += sum;
}
}
}
}

public static void main(String[] args) throws Exception {
final Account account1 = new Account(0, 50L);
final Account account2 = new Account(1, 100L);

final CyclicBarrier barrier = new CyclicBarrier(2);
final Thread thread1 = new Thread(new Runnable() {

@Override
public void run() {
try {
barrier.await();
trasfer(account1, account2, 10L);
} catch (InterruptedException e) {
return;
} catch (BrokenBarrierException e) {
return;
}
}
}, "thread1");

final Thread thread2 = new Thread(new Runnable() {

@Override
public void run() {
try {
barrier.await();
trasfer(account2, account1, 10L);
} catch (InterruptedException e) {
return;
} catch (BrokenBarrierException e) {
return;
}
}
}, "thread2");
thread1.start();
thread2.start();
}
}

И конечно же неизбежно получаем deadlock.
Не убивая приложение запускаем jvisualvm, который входит в стандартную поставку sun jdk 1.6 - или как вариант классический jconsole - и подключаемся к нему:Естественно видим, что наши thread1 и thread2 висят на мониторе. Жмём Thread Dump и наблюдаем состояние всех потоков - и в том числе и обнаруженный dead lock:
Found one Java-level deadlock:
=============================
"thread2":
waiting to lock monitor 0x02bc12b4 (object 0x22fcbe60, a com.db.DeadLock$Account),
which is held by "thread1"
"thread1":
waiting to lock monitor 0x02c2a2fc (object 0x22fcbe78, a com.db.DeadLock$Account),
which is held by "thread2"

Java stack information for the threads listed above:
===================================================
"thread2":
at com.db.DeadLock.trasfer(DeadLock.java:26)
- waiting to lock <0x22fcbe60> (a com.db.DeadLock$Account)
- locked <0x22fcbe78> (a com.db.DeadLock$Account)
at com.db.DeadLock$2.run(DeadLock.java:63)
at java.lang.Thread.run(Thread.java:619)
"thread1":
at com.db.DeadLock.trasfer(DeadLock.java:26)
- waiting to lock <0x22fcbe78> (a com.db.DeadLock$Account)
- locked <0x22fcbe60> (a com.db.DeadLock$Account)
at com.db.DeadLock$1.run(DeadLock.java:48)
at java.lang.Thread.run(Thread.java:619)

Found 1 deadlock.

В jconsole есть функциональность как Detect Deadlock

Важное замечание: Получить thread dump можно:
$ kill -3 pid
где pid - pid java процесса

2 комментария:

.sid комментирует...

Кстати, для синхронизации main потока и двух исполняющих задачу потоков можно вместо двух CountDownLatch'ей использовать один CyclicBarrier на трех participiant'ов, будет проще.

Владимир Долженко комментирует...

@ .sid
согласен полностью, красивее получается - обновил код