ずっと前に、私はJavaリファレンスブックから「Javaにはデッドロックを処理するメカニズムがありません。デッドロックが発生したことすら知りません」という文を保存しました。(Head First Java 2nd Edition、p.516)
それで、それはどうですか?Javaでデッドロックケースをキャッチする方法はありますか?つまり、デッドロックケースが発生したことをコードが理解する方法はありますか?
ずっと前に、私はJavaリファレンスブックから「Javaにはデッドロックを処理するメカニズムがありません。デッドロックが発生したことすら知りません」という文を保存しました。(Head First Java 2nd Edition、p.516)
それで、それはどうですか?Javaでデッドロックケースをキャッチする方法はありますか?つまり、デッドロックケースが発生したことをコードが理解する方法はありますか?
JDK 1.5 以降、発生したデッドロックを見つけて検査するための非常に便利なメソッドがjava.lang.management
パッケージに含まれています。クラスのfindMonitorDeadlockedThreads()
およびfindDeadlockedThreads()
メソッドを参照してください。ThreadMXBean
これを使用する可能な方法は、これを行う別のウォッチドッグ スレッド (または定期的なタスク) を持つことです。
サンプルコード:
ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
long[] ids = tmx.findDeadlockedThreads();
if (ids != null) {
ThreadInfo[] infos = tmx.getThreadInfo(ids, true, true);
System.out.println("The following threads are deadlocked:");
for (ThreadInfo ti : infos) {
System.out.println(ti);
}
}
JConsoleは、実行中のアプリケーションのデッドロックを検出できます。
JDK 5 および 6 は、保持されているロック情報を完全なスレッド ダンプ (kill -3、jstack、jconsole などで取得) にダンプします。JDK 6 には、ReentrantLock および ReentrantReadWriteLock に関する情報も含まれています。この情報から、ロック サイクルを見つけることでデッドロックを診断できます。スレッド A はロック 1 を保持し、スレッド B はロック 2 を保持し、A が 2 を要求しているか、B が 1 を要求しています。
他の分析ツールでは、デッドロックが発生していなくても、潜在的なデッドロックを実際に見つけることができます。OptimizeIt、JProbe、Coverity などのベンダーが提供するスレッド ツールは、一見の価値があります。
並行パッケージを使用すると、デバッグが非常に困難なタイプのデッドロックがあることに注意してください。そこに ReentrantReadWriteLock があり、1 つのスレッドが読み取りロックを取得し、(たとえば) 書き込みロックの取得を待機している他のスレッドが保持するモニターに入ろうとします。デバッグが特に難しいのは、誰が読み取りロックに入ったかの記録がないことです。それは単なるカウントです。スレッドが例外をスローして終了し、読み取りカウントが 0 以外になった可能性さえあります。
前述の findDeadlockedThreads メソッドが取得できないデッドロックの例を次に示します。
import java.util.concurrent.locks.*;
import java.lang.management.*;
public class LockTest {
static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) throws Exception {
Reader reader = new Reader();
Writer writer = new Writer();
sleep(10);
System.out.println("finding deadlocked threads");
ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
long[] ids = tmx.findDeadlockedThreads();
if (ids != null) {
ThreadInfo[] infos = tmx.getThreadInfo(ids, true, true);
System.out.println("the following threads are deadlocked:");
for (ThreadInfo ti : infos) {
System.out.println(ti);
}
}
System.out.println("finished finding deadlocked threads");
}
static void sleep(int seconds) {
try {
Thread.currentThread().sleep(seconds*1000);
} catch (InterruptedException e) {}
}
static class Reader implements Runnable {
Reader() {
new Thread(this).start();
}
public void run() {
sleep(2);
System.out.println("reader thread getting lock");
lock.readLock().lock();
System.out.println("reader thread got lock");
synchronized (lock) {
System.out.println("reader thread inside monitor!");
lock.readLock().unlock();
}
}
}
static class Writer implements Runnable {
Writer() {
new Thread(this).start();
}
public void run() {
synchronized (lock) {
sleep(4);
System.out.println("writer thread getting lock");
lock.writeLock().lock();
System.out.println("writer thread got lock!");
}
}
}
}
一般に、Java はデッドロック検出を提供しません。同期されたキーワードと組み込みのモニターにより、明示的なロックを使用する言語よりも、デッドロックについて推論することがいくらか難しくなります。
ロック スキームを理解しやすくするために、java.util.concurrent.Lock ロックなどを使用するように移行することをお勧めします。実際、デッドロック検出機能を備えたロック インターフェイスの独自の実装を簡単に作成できます。アルゴリズムは、基本的にロック依存グラフをトラバースし、サイクルを探すことです。
すべてのスレッドが同じ順序でロックを要求し、解放するという単純なルールに従えば、デッドロックを回避できます。このようにして、デッドロックが発生する状況に陥ることはありません。
食事の哲学者の問題でさえ、左右のスプーンの相対的な概念を使用しているため、この規則に違反していると見なすことができます。その結果、スプーンの異なる割り当て順序を使用して異なるスレッドが発生します。スプーンに一意の番号が付けられていて、哲学者全員が最初に番号の最も小さいスプーンを取得しようとした場合、デッドロックは発生しません。
私の意見では、予防は治療よりも優れています。
これは、スレッドが適切に機能することを保証するために私が従うのが好きな 2 つのガイドラインのうちの 1 つです。もう 1 つは、各スレッドが自分の実行に対して単独で責任を負うことを保証することです。これは、任意の時点で実行していることを完全に認識している唯一のスレッドであるためです。
つまり、Thread.stop
呼び出しがないことを意味し、グローバル フラグ (またはメッセージ キューなど) を使用して、別のスレッドにアクションを実行するように指示します。次に、そのスレッドに実際の作業をさせます。
Java はデッドロックを検出できます (実行時ではありませんが、診断して報告することはできます)。
たとえば、次の「Saurabh M. Chande」コードのわずかに変更されたバージョンを使用する場合 (Java に変更し、実行ごとにロックを保証するためにタイミングを追加しました)。次のように入力すると、実行してデッドロックが発生します。
kill -3 PID # where 'PID' is the Linux process ID
次の情報を含むスタック ダンプが生成されます。
Found one Java-level deadlock:
=============================
"Thread-0":
waiting to lock monitor 0x08081670 (object 0x7f61ddb8, a Deadlock$A),
which is held by "main"
"main":
waiting to lock monitor 0x080809f0 (object 0x7f61f3b0, a Deadlock$B),
which is held by "Thread-0"
Java 5 を使用しているfindMonitorDeadlockedThreads()
場合は、の呼び出しで取得できる ThreadMXBean のメソッドを呼び出すことができますjava.lang.management.ManagementFactory.getThreadMXBean()
。これにより、オブジェクト モニターのみが原因のデッドロックが検出されます。Java 6 では、「所有可能なシンクロナイザー (および など)findDeadlockedThreads()
によって引き起こされるデッドロックも検出されます。ReentrandLock
ReentrantReadWriteLock
これらのメソッドを呼び出すとコストがかかる可能性があるため、トラブルシューティングの目的でのみ使用する必要があることに注意してください。
あなたが尋ねたものとは正確には異なりますが、デッドロックが発生した場合、プロセス ID で「kill -3」を実行すると、スレッド ダンプが stdout にダンプされます。また、1.6 jvm には、GUI 方式で同じことを行うツールがいくつかあります。
コマンドラインから実行していて、デッドロックが発生していると思われる場合は、Windows で ctrl+break (UNIX では ctrl+\) を試して、スレッド ダンプを取得してください。http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/gbmps.htmlを参照してください。
JavaSpecialists の Dr. Heinz Kabutz は、Java デッドロックに関する楽しく有益なニュースレターを書き、別のニュースレターで ThreadMXBean と呼ばれるものについて説明しています。それらの間に、問題の良いアイデアと、独自のインストルメンテーションを行うためのいくつかの指針が得られるはずです。
Eclipse でデバッグしている場合は、アプリケーションを一時停止して (デバッグ ビューでアプリを選択し、デバッグ ツールバーの小さな || ボタンを選択)、デッドロックを報告できます。
例については、 http://runnerwhocodes.blogspot.com/2007/10/deadlock-detection-with-eclipse.htmlを参照してください。
久しぶりに、デッドロックの最も簡単な例を書くことができます。コメントは大歓迎です。
Class A
{
synchronized void methodA(B b)
{
b.last();
}
synchronized void last()
{
SOP(“ Inside A.last()”);
}
}
Class B
{
synchronized void methodB(A a)
{
a.last();
}
synchronized void last()
{
SOP(“ Inside B.last()”);
}
}
Class Deadlock implements Runnable
{
A a = new A();
B b = new B();
// Constructor
Deadlock()
{
Thread t = new Thread();
t.start();
a.methodA(b);
}
public void run()
{
b.methodB(a);
}
public static void main(String args[] )
{
new Deadlock();
}
}
Deadlock クラスのコードを少し変更する必要があります
デッドロック() { Therad t = 新しいスレッド (これ); // 修正済み t.start(); System.out.println(); //遅延する命令 a.methodA(b); }
また、上記のコードは常にデッドロックを引き起こすとは限りません。