【デッドロック】
同期は競合を防ぐ合理的で便利な仕組みであるが,使い方を誤るとスレッドを永遠に
停止させてしまう。たとえば,ロックを永遠に取得したままの同期コードがある場合,その
ロックの解放を待つ別の同期コードは,永遠に停止したままになってしまう。このような
状況をスターべーション(starvation,「餓死」という意味)と呼ぶ。
スターべーションのもっとも発生しやすくもっとも深刻な状況が,デッドロック(dead lock)で
ある。デッドロックは,複数のロックを取得する必要がある場合に発生する可能性がある。
デッドロックが起こると,複数のスレッドが永遠に停止したままになる。
身近な例で考えてみよう。紙が一枚,鉛筆が一本有るとする。今,2人の人物・太郎君と
花子さんに「その鉛筆でその紙に,"こんにちは"と書け。」と同時に命令したとする。太郎
君が最初に紙を手にし,花子さんが鉛筆を手にした。するとどうだろう。太郎君には鉛筆が
足りないし,花子さんには紙が足りない。結局,2人とも相手が自分と必要としているもの
を手放すまで目的を達することができずに止まってしまうのである。
(実演すると良く分かる。友人三人でやってみよう。そのうち一人は命令を下す役。)
List 8〜10に,もう少し具体的なデッドロックの発生例を示す。List 8 と List 9 は,ある
銀行口座から別の銀行口座に送金する処理を行うプログラムである。List 8 は口座クラス
Accountの定義である。List 8-①は,
・現在開設されている口座番号の最大値 accountNumMax
・口座番号 accountNum,
・残高 balance
という3つのフィールドと,それらの値を設定するコンストラクタである。
List 8-②〜④は,同期アクセッサの定義である。
このような例では,基本的には下図の様に同期処理を行えば良さそうである。


List 9は,送金トランザクションを行うスレッドクラスTransferTransactionの定義である。
(参考:トランザクション(transaction)とは,「取引」処理というような意味で,コンピュータ業界では,
要求に応えて行う一件一件の処理のことを表す)
List 9-①は
・送金元口座 source
・送金先口座 dest
・送金金額 amount
という3つのフィールドと,それらを設定するコンストラクタである。
その後の transfer( )メソッドの List 9-② が送金を行う部分である。送金を行うので,
・送金元口座オブジェクト
と
・送金先口座オブジェクト
の両方を変更する必要がある(送金元の口座残高を減らして,送金先の口座残高をその分増やす)。
そこで,送金元口座オブジェクト source と送金先口座オブジェクト dest を synchronized( source ) {
synchronized( dest ) {
// 送金処理
}
}
というように,2重にロック用オブジェクトに指定し,その中で送金処理を行っている。そして,
List 9-③で,run( ) メソッドから transfer( ) メソッドを呼び出して,送金を実行するようになっ
ている。
List 10は,トランザクションを処理するシステムを表す TransactionSystem クラスの定義
である。List 10-①で送金元口座オブジェクト a1 と,送金先口座オブジェクト a2 を生成し
ている。そして,List 10-②では, a1 から a2 への送金トランザクションスレッドと, a2 から a1
への送金トランザクションスレッドを生成して,それぞれ実行を開始させている。
List 10 TransactionSystem.java

List 8〜10を実行すると,デッドロックが発生して2つのスレッドは停止してしまう(そのた
めプログラムも終了しない)。なぜであろうか。
デッドロックが発生する様子を,Fig.8(下図)に示す。

Fig. 8 を解説しよう。
・a1 から a2 へ送金するスレッドを a とし,
・a2 から a1 へ送金するスレッドを b とする。
そして,スレッド a が 送金を行うtransfer( ) を呼び出したとしよう(Fig.8-①)。ついで
スレッド b も transfer( ) を呼び出す(Fig.8-②)。
再びスレッド a に処理が移り,スレッド a は同期コード部に入って source,つまり a1 のロック
を取得する(Fig.8-③)。続いて,スレッド b も同期コード部に入って, source,つまり a2 の
ロックを取得する(Fig.8-④)。
つぎに,スレッド a に処理が移って dest(つまり a2)のロックを取得しようとする(fig.8-⑤)。
ここからが問題で, a2 のロックはすでにスレッド b の同期コードによって取得されているので,
スレッド a の同期コードは a2 のロックの解放を待つために停止する。処理がスレッド b に
うつって, dest(つまり a1)のロックを取得しようとする(Fig.8-⑥)。しかし,ここでも a1 のロック
はすでにスレッド a の同期コードによって取得されているため,スレッド b の同期コードは a1 の
ロックの解放を待つために停止する。
つまり,2つのスレッドの同期コードがお互いに,相手が保持しているロックの解放を待つために,
停止しているのだが,両方とも実行が停止しているので,ロックを解放することはない。
その結果,これら2つのスレッドは永遠に停止することになってしまうのである。
では,このようなデッドロックをどのようにしたら回避できるだろうか。次にデッドロックの典型的
な回避方法を紹介する。
次に進む