西海岸より

つらつらざつざつと

デッドロックの検出を試す

デッドロックが発生した場合にどうしたら良いのか、実際に意図的にデッドロックが発生するコードを作り、スレッドダンプを見て試してみた。(MacOSX 10.5.7, java1.6.0_07)

デッドロックが発生するコード

public class DeadLocker{
    public static void main(String[] args){
        final LockObject lock_a = new LockObject("lock_a");
        final LockObject lock_b = new LockObject("lock_b");
        
        Worker workerA = new Worker("WorkerA", lock_a, lock_b);
        Worker workerB = new Worker("WorkerB", lock_b, lock_a);
        workerA.start();
        workerB.start();
    }
}

class LockObject{
    private final String name;
    public LockObject(String name){
        this.name = name;
    }
    public String toString(){
        return name;
    }
}

class Worker extends Thread{
    private final Object lock1;
    private final Object lock2;
    public Worker(String name, Object lock1, Object lock2){
        super(name);
        this.lock1 = lock1;
        this.lock2 = lock2;
    }
    public void run(){
        for(int i=0; i<1; i++){
            System.out.println("Thread("+this+") start lock("+ lock1 +")");
            synchronized(lock1){
                Thread.yield();
                System.out.println("Thread("+this+") start lock("+ lock2 +")");
                synchronized(lock2){
                
                }
                System.out.println("Thread("+this+") end lock("+ lock2 +")");
            }
            System.out.println("Thread("+this+") end lock("+ lock1 +")");
        }
    }
}
  • 実行結果
$ java DeadLocker
Thread(Thread[WorkerA,5,main]) start lock(lock_a)
Thread(Thread[WorkerB,5,main]) start lock(lock_b)
Thread(Thread[WorkerA,5,main]) start lock(lock_b)
Thread(Thread[WorkerB,5,main]) start lock(lock_a)

ここで処理がとまる。(タイミングの問題なので、うまく行く場合もあります。)

WorkerA ----> [lock_a獲得] ----> [lock_bのロック待ち] 
WorkerB ----> [lock_b獲得] ----> [lock_aのロック待ち]

とまぁとてもシンプルなもの。

スレッドダンプを取得(jstack)

一つ目はJava6から標準サポートのjstackを使う方法。引数に対象とするjavaのPIDを指定するとスレッドダンプが表示される。

jstack PID
  • まずjpsjavaのPIDを調べる
$ jps
2653 Jps
2646 DeadLocker
  • jstackでDeadLockerのPIDを指定して実行
$jstack 2591
2009-07-16 00:05:49
Full thread dump Java HotSpot(TM) 64-Bit Server VM (1.6.0_07-b06-57 mixed mode):

"Attach Listener" daemon prio=9 tid=0x000000010185c800 nid=0x110642000 waiting on condition [0x0000000000000000..0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" prio=5 tid=0x0000000101801000 nid=0x100401000 waiting on condition [0x0000000000000000..0x0000000100400aa0]
   java.lang.Thread.State: RUNNABLE

"WorkerB" prio=5 tid=0x000000010185b000 nid=0x11053f000 waiting for monitor entry [0x000000011053e000..0x000000011053ead0]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at Worker.run(DeadLocker.java:39)
	- waiting to lock <0x00000001050873d8> (a LockObject)
	- locked <0x00000001050873f0> (a LockObject)

"WorkerA" prio=5 tid=0x000000010185a000 nid=0x11043c000 waiting for monitor entry [0x000000011043b000..0x000000011043bad0]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at Worker.run(DeadLocker.java:39)
	- waiting to lock <0x00000001050873f0> (a LockObject)
	- locked <0x00000001050873d8> (a LockObject)

"Low Memory Detector" daemon prio=5 tid=0x000000010184d800 nid=0x10fff5000 runnable [0x0000000000000000..0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"CompilerThread1" daemon prio=9 tid=0x000000010184c800 nid=0x10fef2000 waiting on condition [0x0000000000000000..0x000000010fef0290]
   java.lang.Thread.State: RUNNABLE

"CompilerThread0" daemon prio=9 tid=0x000000010184a800 nid=0x10fdef000 waiting on condition [0x0000000000000000..0x000000010fded3e0]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=9 tid=0x000000010184a000 nid=0x10fcec000 runnable [0x0000000000000000..0x000000010fceb7d0]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=8 tid=0x0000000101829800 nid=0x10fbe9000 in Object.wait() [0x000000010fbe8000..0x000000010fbe8ad0]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x0000000105001d50> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116)
	- locked <0x0000000105001d50> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)

"Reference Handler" daemon prio=10 tid=0x0000000101828800 nid=0x10fae6000 in Object.wait() [0x000000010fae5000..0x000000010fae5ad0]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x0000000105001a50> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:485)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
	- locked <0x0000000105001a50> (a java.lang.ref.Reference$Lock)

"VM Thread" prio=9 tid=0x0000000101820800 nid=0x1007f4000 runnable 

"VM Periodic Task Thread" prio=10 tid=0x000000010184f000 nid=0x1100f8000 waiting on condition 

"Exception Catcher Thread" prio=10 tid=0x0000000101802000 nid=0x100504000 runnable 
JNI global references: 587


Found one Java-level deadlock:
=============================
"WorkerB":
  waiting to lock monitor 0x00000001017c3360 (object 0x00000001050873d8, a LockObject),
  which is held by "WorkerA"
"WorkerA":
  waiting to lock monitor 0x00000001017c1fb0 (object 0x00000001050873f0, a LockObject),
  which is held by "WorkerB"

Java stack information for the threads listed above:
===================================================
"WorkerB":
	at Worker.run(DeadLocker.java:39)
	- waiting to lock <0x00000001050873d8> (a LockObject)
	- locked <0x00000001050873f0> (a LockObject)
"WorkerA":
	at Worker.run(DeadLocker.java:39)
	- waiting to lock <0x00000001050873f0> (a LockObject)
	- locked <0x00000001050873d8> (a LockObject)

Found 1 deadlock.

この中で後半の「Found one Java-level deadlock:」箇所以降がデッドロック検出した内容を表していて、ソース上の発生箇所も書かれているなど後は見ればわかる通り。

スレッドダンプを取得(OSからシグナルを送る)

Macの場合にはJava5でもjstackが使えるが、Linuxなどでは使えない場合には、killコマンドでシグナルを送ることでスレッドダンプの出力が可能。(Windowsの場合はJava - スレッドダンプの取り方を参照)

kill -3 PID

ただし、スレッドダンプの結果はjavaを実行しているコンソールに出力されるため、スレッドダンプを出力したいプロセスがバックグラウンドで実行されている場合には見られない。この場合には、予め標準出力、標準エラーをファイルに出力させるようにしておくことが必要。

実行時に以下のように標準エラーの出力先を標準出力にし、標準出力のファイル出力先を記述しておくことで、ファイルで確認できる。下記の場合にはsysout.txtにコンソールの内容が出力される。

java DeadLocker > sysout.txt 2>&1

中身はjstackを使った時と同じ。比べてみてわかる通りJava6系ならjstackを使った方が利便性が高い。またMacではJava5でjstackを使えるが、killを使う場合と比べて出力される情報量が(なぜか)少ないので、Java5ではOSからシグナルを送る後者の方法が一般的になると思う。

参考