スレッドデッドロックは、マルチスレッドのJavaアプリケーションであれば起こり得るものです。 マルチスレッドのアプリケーションが実装された瞬間から、デッドロック問題が発生する可能性があります。

例えば: アプリケーションを公開リリースに向けて、完全なテスト運用を開始し、追加テストも行い、 最終的に重要な基幹システムとしてデプロイするとします。 アプリケーションの利用が本格的に増して、重要なコンポーネントの応答が突然に止まります。 この時、システム管理者が出来る唯一のことは、システムを再開させるために、アプリケーションを強制終了して再起動させることです。

この障害に伴い、よくあることは、アプリケーションユーザーからシステムの不具合についてのクレームが寄せられ、 営業ロス、その他システムの停滞による損害などを訴えてくることでしょう。 アプリケーションの開発者には、アプリケーションがフリーズしたことが知らされ、即に解決するように言われます。 運悪く、たいていの場合、アプリケーションがフリーズした原因が何か把握することは、ほとんど無理でしょう。 つまり、開発者にとっては、何が原因だったのかヒントもなく、その問題そのものすら把握できないまま、手探り状態で問題を解決しようとしているようなものです。 これは、「もう一度やってみて、どうなるか見てみよう」的な対応になることが多く、誰一人として歓迎するものではないでしょう。

現実の世界においてのアプリケーションで、デッドロック問題を修正することは非常に難しく、症状を再現することはほとんど不可能です。 これは、多くのスレッドが利用されればされるほど、デッドロックが発生する可能性も増える、という現実があるためです。 デッドロックが発生した場所を特定するだけでなく、そこに導いてしまうイベントの組み合わせも検証する必要があり、 ディベロッパー(開発者)にとってみれば、多大な時間を費やします。

Wrapperがデッドロックを検知すると、まず、どのスレッドがどのオブジェクトと絡み合っているか、厳密に詳細ログをとります。 そして、すぐにJVMを再起動させ、最短の停滞時間でシステムをリカバリー(回復)させることができます。 つまり、これで、Javaアプリケーションが稼働可能な状態を維持するだけでなく、 問題を報告するのに必要な全ての情報を残すことができるため、問題修復にも有効です。 Deadlock

デッドロックとは?

あるプログラムにおいて複数のスレッドが、決して有効にならないオブジェクトへのアクセス待ちが停滞することで、 デッドロックが発生します。

トムとフレッドの2人の例をあげて考えてみましょう。両者は生活のために「物書き」の仕事をしています。 彼らは、ノートとペンを机から持ってきて、物書きを終えたら、ノートとペンを机へ戻す必要があります。 両者はとても強情で、一旦、書き始めると、作業を終えるまでノートとペンを机へ決して戻しません。

トムがノートを取りました、フレッドがペンを取りました、ここで何が起きるでしょうか? トムはペンが机に戻るのを待ち、フレッドはノートが机に戻るのを待っています。 これが「デッドロック」状態です。 どちらともエンドレスで永久に待ち続けてしまうため、決して前に進むことはありません。

トムとフレッド、どちらかが結局は待ちくたびれて、諦めてノートかペンかを机に戻してくれれば良いのですが、 プログラムの場合、そうはいきません。 デッドロック状況に陥った2つのスレッドがずっと待ち続けることになります。 どこかの時点で、マネージャーがノートがないことに気がつき、これでは問題が起きると察知してくれれば良いのですが。

このようなデッドロック問題の再製や修復が難しくなってしまうのは、単なるタイミングの問題です。 トムとフレッドには、めったに同時に書きものをする必要性が発生しなかったため、 何年間も問題なく共に仕事をしてきました。 しかし、2人が物書きの仕事をする以上、この問題が発生する可能性がすぐに出てきます。

このジレンマをどのように解決すればいいでしょうか?

この問題を解決するためには、 トムもフレッドも、彼らがノートを取ると同時に、常にすぐにペンも取るべきだと理解していればよかったのです。 それでも彼らのどちらかがノートかペンかが机に戻るのを待つ必要があるケースもまだ発生するでしょうが、 エンドレスで永久に停滞してしまうことはありません。

ソリューション

上記で2人の作業例を挙げたとおり、その原因は明白であり、簡単に解決することができます。 しかしながら、数十個のリソースや何千ものソースコードを持つ非常に大きなアプリケーションソフトにおいて、 問題点を指摘するのは非常に困難であり、その問題を解決することも大変です。 実際のデッドロックでは、いくつかのリソースとスレッドが絡むこともあり、 システム開発者が想定しないような組み合わせが原因で発生することがあります。

テスト環境で問題が見られなくても、そのソフト本質に問題を抱えるデッドロックは、 実際の本稼動で取り扱う現実のデータが大量でサイズも大きく種類も多岐に及ぶため、 本稼動に入ってからデッドロック問題が発生する可能性が非常に高くなるのです。 上記の2人の作業例のように、作業量が少ない場合には、ほとんど問題なくシステムは動作するでしょう。 しかし両者が忙しくなると、問題が発生する可能性が大きくなります。

両者に同じ作業リストを持たせてお互いの仕事(動作)を把握させればいいのですが、 現実には、巨大システムの開発設計において分業作業であるため、両者の動作リストをすり合わせることは困難です。 どちらか1人での作業で問題がなくても、両者が同時に動き出すと、問題が発生するのです。

Java Service Wrapperがアプリケーションレベルのデッドロックの発生を防ぐことができない場合でも、 Wrapperには高度なデッドロック検知機能を装備しているため、 何かがおかしいと人員的な問題察知の前に、Wrapperが問題を検知したり解決します。 同時に、Wrapperは「何が発生したのか」の詳細な情報を収集し、記録をログに残します。 これで、開発者が問題を理解し、問題のあるソース改善に素早く対応することができ、作業が楽になることでしょう。

Wrapperには動作しているアプリケーション全体をモニタリング(監視)機能がありますが、 事実上、何もパフォーマンスに影響を及ぼしません。 アプリケーション内にデッドロックを検知すると、以下のようなWrapperのログファイルでレポートを発行します。

デッドロック発生時のログ例:
WrapperManager エラー: 2個のデッドロックのスレッド見つかりました!
WrapperManager エラー: =============================
WrapperManager エラー: "Worker-1" tid=18
WrapperManager エラー:   java.lang.Thread.State: BLOCKED
WrapperManager エラー:     場所: com.example.Worker1.pickUpPaper(Worker1.java:64)
WrapperManager エラー:       - 応答待機中: <0x000000002fcac6db> (a com.example.Paper) 所有元: "Worker-2" tid=17
WrapperManager エラー:     場所: com.example.Worker1.pickUpPen(Worker1.java:83)
WrapperManager エラー:       - ロック済み <0x0000000029c56c60> (a com.example.Pen)
WrapperManager エラー:     場所: com.example.Worker1.logMessage(Worker1.java:22)
WrapperManager エラー:     場所: com.example.Worker1.run(Worker1.java:42)
WrapperManager エラー:
WrapperManager エラー: "Worker-2" tid=17
WrapperManager エラー:   java.lang.Thread.State: BLOCKED
WrapperManager エラー:     場所: com.example.Worker2.pickUpPen(Worker2.java:83)
WrapperManager エラー:       - 応答待機中: <0x0000000029c56c60> (a com.example.Pen) 所有元: "Worker-1" tid=18
WrapperManager エラー:     場所: com.example.Worker2.pickUpPaper(Worker2.java:64)
WrapperManager エラー:       - ロック済み <0x000000002fcac6db> (a com.example.Paper)
WrapperManager エラー:     場所: com.example.Worker2.logMessage(Worker2.java:22)
WrapperManager エラー:     場所: com.example.Worker2.run(Worker2.java:42)
WrapperManager エラー:
WrapperManager エラー: =============================

デッドロックが検知された後の処理アクションの実行を、Wrapperに設定することができます。 ほとんどのケースでは、メール通知を送信して、アプリケーションを再起動させることが最良の方法でしょう。 通知メールには、上記の出力が含めるため、わりと簡単に問題を認識でき修復が手軽にできるでしょう。 そして、アプリケーションを再起動させるため、不要な遅延なくリカバリー(回復)でき、ユーザーに与える停滞の影響を最小限に抑えることができます。

まず、レポートを見ることで、「Worker-1」や「Worker-2」スレッドが原因でデッドロックが発生した事実を とても簡単に確認することができ、それぞれの コールスタック内の問題の箇所の正確な場所を把握することもできます。 その出力情報で、どの特定のオブジェクトインスタンスが失敗したのか、さらに明確になります。

なおその上、JVMが自動的に再起動されるため、 ユーザーに与える影響を抑えられ、停滞時間もほんの一瞬で済みます。

Wrapperは次のような様々な危機的な問題の検知に役立ちます: フリーズデッドロッククラッシュアプリケーションエラーメモリリークJVM終了コードに応答するなど。

テクニカルソリューション

Java Service Wrapperのデッドロック検知機能を有効にする方法は、 ご利用のWrapperコンフィギュレーションファイルに、 2,3つのプロパティ設定を追加するだけで簡単に実現できます。

Java Service Wrapperに同梱して配布しているTestWrapperサンプルアプリケーションには、 デフォルトでこの機能を有効にしてあります。 単純に、そのアプリケーションを起動して「デッドロック生成」ボタンをクリックすれば、その動作を確認することができます。

単純なデッドロック検知

対応バージョン :3.5.0
対応エディション :プロフェッショナル版スタンダード版コミュニティー版 (未対応)
対応プラットフォーム :WindowsMac OSXLinuxIBM AIXFreeBSDHP-UXSolarisIBM z/OSIBM z/Linux

Java Service Wrapperでは、

デッドロックを検知させる設定例:
wrapper.check.deadlock=TRUE
wrapper.check.deadlock.interval=60
wrapper.check.deadlock.action=RESTART
wrapper.check.deadlock.output=FULL

メール通知

対応バージョン :3.5.0
対応エディション :プロフェッショナル版スタンダード版 (未対応)コミュニティー版 (未対応)
対応プラットフォーム :WindowsMac OSXLinuxIBM AIXFreeBSDHP-UXSolarisIBM z/OSIBM z/Linux

上記の例で、デッドロックの原因をログ化し、アプリケーションをリカバリー(回復)させる間に、 その問題をメール通知で受信できるのも便利です。 以下の設定では、デッドロックをログ化し、JVMを再起動し、メール通知を送信します:

デッドロック時、メール通知を送信する設定例:
# Check for deadlocks
wrapper.check.deadlock=TRUE
wrapper.check.deadlock.interval=60
wrapper.check.deadlock.action=RESTART
wrapper.check.deadlock.output=FULL

# Send notification email
wrapper.event.default.email.smtp.host=smtp.example.com
wrapper.event.default.email.subject=[%WRAPPER_HOSTNAME%:%WRAPPER_NAME%:%WRAPPER_EVENT_NAME%] イベント通知
wrapper.event.default.email.sender=myapp-noreply@example.com
wrapper.event.default.email.recipient=sysadmins@example.com
wrapper.event.jvm_deadlock.email=TRUE
wrapper.event.jvm_deadlock.email.body=Messaging Serverにデッドロック検知しました。\n\n確認してください\n
wrapper.event.jvm_deadlock.email.maillog=ATTACHMENT

外部コマンドの実行

対応バージョン :3.5.0
対応エディション :プロフェッショナル版スタンダード版 (未対応)コミュニティー版 (未対応)
対応プラットフォーム :WindowsMac OSXLinuxIBM AIXFreeBSDHP-UXSolarisIBM z/OSIBM z/Linux

デッドロックが発生したとき、本来ならば綺麗に削除されるはずのデータが不明確な状態で残る場合があります。 通常ならば、トランザクションを利用したり、あるいはJavaアプリケーションに回復機能を持たせることも良いアイデアなのですが、 そのような機能はアプリケーション開発チームによる改変が必要になることも多く、改善には時間を要します。 次の良い解決策が出るまでにも何とか、システム管理側はモノゴトがしっかり動作するようにさせる任務もあります。

そこで便利なのが、Java Service Wrapperでは、外部コマンド、アプリケーション、バッチファイルを、 イベントに応じて実行することができます。

以下の設定では、

デッドロック時、外部コマンドの実行の設定例:
# Check for deadlocks
wrapper.check.deadlock=TRUE
wrapper.check.deadlock.interval=60
wrapper.check.deadlock.action=RESTART
wrapper.check.deadlock.output=FULL

# Run external batch file in response to a deadlock.
wrapper.event.jvm_deadlock.command.argv.1=../bin/DeadlockCleanup.bat
wrapper.event.jvm_deadlock.command.block=TRUE

参照: デッドロック

Java Service Wrapper では、必要なコンフィギュレーション設定を含んだ完全なパッケージを提供しており、 それを活用することで、皆様の求めるニーズに合った動作を実現させることができます。 上記の例の他に、工夫次第で様々なことが実現可能となりますので、それぞれ個別にプロパティページをご覧ください。