現場から学ぶWebアプリ開発のトラブルハック 第11回
スレッドダンプの森で覚えた
死のロックへの違和感
株式会社NTTデータ 基盤システム事業本部
茂呂 範
2008/9/5
森の中で覚えた違和感
違和感を感じたのは、次の個所だ。
| ログ1 スタックトレース1(get)の強調版 |
at troublehack11.DBConnectionPool.get()(DBConnectionPool.java:27) |
| ログ3 スタックトレース2(wait)の強調版 |
at java.lang.Object.wait(Native Method) |
なんと、ほかのスレッドが待たされている排他制御個所と、このスレッドが待ち状態になっている個所で、利用されているロック用のオブジェクトが異なるのだ。
■ Javaで排他制御を必要とする際に見落としがちなもの
Javaで排他制御を必要とするプログラムを書く際に見落としがちなものの1つに、waitメソッドを利用した際のスレッドの待機時での、ロックオブジェクトに対するロック状態がある。ここでいったん、次のサンプルプログラムを見てみよう。
| プログラム1 2重ロック |
public class DualLock implements Runnable { |
賢明な読者なら、このプログラムの問題点がすぐに分かったと思う。このサンプルプログラムは、2つのスレッドがlock1、lock2の順番でロックを獲得しながら、最後にlock2でwait状態へと遷移するプログラムだ。
ロックを獲得する順番が、2つのスレッド間で同じであるため、このプログラムはデッドロックを引き起こさない。そのため直感的には、2つのスレッドはともにwait状態へと遷移してほしいところだ。
■ ロックにかかわるJavaのObject.wait()メソッドの仕様
さて、このプログラムのどこに問題が発生するかを説明しよう。
![]() |
| 図2 2重ロックのシーケンス図 |
ロックにかかわるJavaのObject.wait()メソッドの仕様を確認すると、次のような記述がある。
「現在のスレッドはこのオブジェクトのモニターのオーナでなければなりません。スレッドはこのモニターの所有権を解放し、別のスレッドが notify メソッドまたは notifyAll メソッドを呼び出してこのオブジェクトのモニター上で待機するスレッドに通知を出すまで待機します。そのあと、スレッドはモニターの所有権を再度取得するまで待機してから実行を再開します」(引用:ObjectクラスのwaitメソッドのAPIドキュメント)
ここでは、Object.wait()呼び出し時には、Objectのロックを解放すると記載されている。逆にいえば、waitメソッドを呼び出したObject以外の取得済みロックについては解放しないことを意味している。そのため、上記プログラム1を実行すると、次のようなスレッドダンプを得ることになる。
| ログ4 2重ロックのスレッドダンプ |
"Thread2" prio=6 tid=0x018e2400 nid=0x1454 waiting for monitor entry [0x03c0f000..0x03c0fd00] |
このスレッドダンプを読むと、Thread1が「lock2(0x238b5cd8)」でwait状態へと遷移するものの、「lock1(0x238b5cd0)」のロックを解放しないため、Thread2は、lock2でロック獲得待ちのためブロックされている。
気を付けていれば、このようなミスが発生することは少ないと思われるかもしれない。
■ そして、隠れ2重ロックも
では、次のプログラムではどうだろうか。
| プログラム2 隠れ2重ロック |
private Object lock; |
一見、何も問題がないように見えるだろう。しかし、多くのAPIドキュメントには、スレッドがロックを保持してwait状態へと遷移するか否かという記述はない。そのため、SomeClassクラスのsomeMethodメソッドのソースコードを確認せずに、同じような事象が発生しないと自信を持って答えることは難しい。
このように、2重ロックは非常に気持ちの悪い違和感を覚える実装だ。しかし、必ずしも問題が発生する訳ではない。また、システムがレスポンスを返さなくなるという問題の原因特定には至っていない。
そこで、さらに詳細にスレッドダンプを解析することにした。
■ スタックトレースによるスレッドの分類と集計
Javaアプリケーション内の動作状況の全貌(ぜんぼう)を把握するため、筆者はよくスタックトレースによるスレッドの分類と集計を行っている。すべてのスレッドを、その状態ごとに分類し、集計していく作業だ。今回は、Samuraiで赤く表示されているスタックトレースを1つ1つ確認することで、次のような分類と集計を得た。
| 表 スタックトレース集計表 | |||
| スタックトレース種別 | 1回目のスレッドダンプ | 2回目 | 3回目 |
| DBCPにて待機 | 1 | 1 | 1 |
| DBConnectionPool.get | 34 | 42 | 55 |
| DBConnectionPool.release | 15 | 15 | 15 |
これまで確認したスタックトレースとは異なるスレッドが存在した。「DBConnectionPool.release」である。この状態のスレッドは、事象発生後のスレッドダンプよりずっと同じ数だけ存在しており、次のスタックトレースを出力していた。
| ログ5 スタックトレース3(release) |
"TP-Processor-80" prio=6 tid=0x01b16400 nid=0xbf0 waiting for monitor entry [0x055bf000..0x055bfb80] |
このスタックトレースを確認した瞬間、さらなる違和感を体験することとなる。
| Index | |
| 第11回 スレッドダンプの森で覚えた死のロックへの違和感 | |
| Page1 スレッドダンプはトラブルハックに非常に有効 スレッドダンプの森に誘われ |
|
| Page2 森の中で覚えた違和感 |
|
| Page3 翌日、ソースコードを探る 出口への道のりは、3つ 違和感の正体 仲間への信頼と高度な予測 |
|
現場から学ぶWebアプリ開発のトラブルハック バックナンバー 連載インデックスへ»
- 第1回 Webアプリの問題点を「見える化」する7つ道具
- 第2回 “Stop the World”を防ぐコンカレントGCとは?
- 第3回 【実録ドキュメント】そのログ本当に必要ですか?
- 第4回 DBアクセスのトラブルは終盤で発覚しがち……
- 第5回 OutOfMemoryエラー発生!? GCがあるのに、なぜ?
- 第6回 【真夏の夜のミステリー】Tomcatを殺したのは誰だ?
- 第7回 【トラブル大捜査線】失われたコネクションを追え!
- 第8回 肥え続けるTomcatと胃を痛めるトラブルハッカー
- 第9回 JavaのGC頻度に惑わされた年末年始の苦いメモリ
- 第10回 ThreadとHashMapに潜む無限回廊は実に面白い?
- 第11回 スレッドダンプの森で覚えた死のロックへの違和感
- 第12回 アプリ開発でも、よ〜く考えよう。キャッシュは大事だよ
- 第13回 DB操作の“壁”を壊すJPAが起こした「赤壁の戦い」
- 第14回 数百キロのコードでブルー - ドクターTomcat緊急救命
| Java Solution全記事一覧 |
TechTargetジャパン
- Scalaのパッケージ、アクセス修飾子、オブジェクト継承 (2012/5/22)
インポート、パッケージオブジェクト、抽象クラス/抽象メソッド、オーバーライド、final、シールドクラスなども - 基幹系システムでCloud SQLは使えるか試してみた (2012/5/17)
サンプルとしてMRPシステムを作成して動かし、「再帰呼び出し」などのパフォーマンスを測定して検証してみます - アジャイル管理ツール9選+Pivotal Tracker入門 (2012/5/14)
群雄割拠のアジャイルプロジェクト管理ツールを9つ紹介し、特に注目を集めているPivotal Trackerの基本的な使い方を解説します - サーバサイドJSやJavaでWebアプリが作れるXPages (2012/5/11)
Notes/Dominoの資産をサーバサイドJavaScriptやJavaで操作し、HTMLやJavaScript、CSSをUIにできる技術を紹介
|
|
キャリアアップ
スポンサーからのお知らせ
- - PR -
イベントカレンダー
- - PR -

