ここでは、スレッドの動きを少し詳しくみていきます。
デッドロックとは、2つのスレッドが2つのロックを取り合い、互いに相手のスレッドがロックを解放するのを待つ状態のことを言います。
具体例で説明します。
例えば、AさんとBさんがコンビニ弁当を1つ買って、一緒に仲良く食べることになったとします。
しかし、袋を開けるとお箸が1セット(左右の2本)しか入っていませんでした。
2本の内の1本(左)のお箸をAさんが手にとりました。すかさずBさんも片方(右)のお箸を手にとりました。
2人とも不器用なので2本(左右)のお箸がないと弁当を食べることができません。
Aさんは、仕方なくBさんがお箸を置くのを待ちます。
Bさんも同じくAさんがお箸を置くのを待ちます。
・・・・
結局、2人とも決して自分の持っているお箸を譲らなかったので餓死してしまいました。・・・・
めでたし!めでたし!・・・ってめでたくないわー!!
この様にAさんとBさんをスレッドとして見立てた場合、その双方のスレッドがそれ以上処理を進めることが出来なくなった状態をデッドロックといいます。
以下にデッドロックイメージ図、サンプルコードおよび仕様、および結果を示します。
※説明を単純化するために、「2つ(2人)のスレッドが」と説明しましたが、実際の処理では、複数のスレッドでデッドロックが発生する場合もあります。
■デッドロックイメージ図
引き続き、デッドロックの動作を検証したいと思います。検証は、以下の仕様(上記で説明した内容を詳細化)、サンプルコードにて行いたいと思います。
<<サンプルコードの仕様説明>>
・ChopSticksクラス
お箸をあらわすクラスです。
このクラスのインスタンスはお箸2本(左右)のうちのどちらか一方をあらわします。
・DeadLockTestクラス
お箸(ChopSticksクラス)の初期設定、及び食事開始の合図を行います。
例では、お箸を左右の2本用意し、AさんBさんに食事の開始を促します。
・Humanクラス(スレッド処理)
人をあらわすクラスです。
このクラスのインスタンスとして、AさんとBさんを生成させます。
■サンプルコード
7-2-1. ChopSticks.java
class ChopSticks{
private final String partName;
public ChopSticks(String partName){
this.partName = partName;
}
public void getStick(String humanName){
//取得できたお箸を表示
System.out.println(humanName+" が、["+partName+"] を取得!!");
}
}
7-2-2. DeadLockTest.java
class DeadLockTest{
public static void main(String args[]){
//お箸を初期化セット
ChopSticks leftStick = new ChopSticks("左のお箸");
ChopSticks rightStick = new ChopSticks("右のお箸");
//Aさん、Bさんのスレッドインスタンスを生成
Human humanA = new Human("Aさん", leftStick, rightStick);
Human humanB = new Human("Bさん", rightStick, leftStick);
//スレッドスタート(いっただきまーす)
humanA.start();
humanB.start();
}
}
7-2-3. Human.java
class Human extends Thread{
private final String name;
private final ChopSticks stick1;
private final ChopSticks stick2;
public Human(String name, ChopSticks stick1, ChopSticks stick2){
this.name = name;
this.stick1 = stick1;
this.stick2 = stick2;
}
public void run(){
System.out.println(this.name+" が、いただきます しました!!");
for(int i = 0; i < 10; i++){
try{
//コンビ二弁当を食べる
eat();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(this.name+" が、ごちそうさま しました!!");
}
private void eat() throws InterruptedException{
synchronized(this.stick1){
//片方のお箸を取得
this.stick1.getStick(this.name);
//デッドロックが発生しやすくするためスリープを実行
//Thread.sleep(10);
synchronized(this.stick2){
//もう片方のお箸を取得
this.stick2.getStick(this.name);
//デッドロックが発生しやすくするためスリープを実行
//Thread.sleep(10);
}
}
}
}
■実行結果イメージ
「Ctrl」キー+「c」キーを同時押下すれば、終了させることができます。
Aさん が、いただきます しました!! Aさん が、[左のお箸] を取得!! Aさん が、[右のお箸] を取得!! Aさん が、[左のお箸] を取得!! Aさん が、[右のお箸] を取得!! Aさん が、[左のお箸] を取得!! Aさん が、[右のお箸] を取得!! Bさん が、いただきます しました!! Bさん が、[右のお箸] を取得!! Bさん が、[左のお箸] を取得!! Bさん が、[右のお箸] を取得!! Bさん が、[左のお箸] を取得!! Bさん が、[右のお箸] を取得!! Bさん が、[左のお箸] を取得!! Aさん が、[左のお箸] を取得!! Aさん が、[右のお箸] を取得!! Aさん が、[左のお箸] を取得!! Aさん が、[右のお箸] を取得!! Aさん が、[左のお箸] を取得!! Aさん が、[右のお箸] を取得!! Aさん が、[左のお箸] を取得!! Bさん が、[右のお箸] を取得!! _
やはり、途中で止まってしまいました。下からの2行の出力を見る限り、以下の様な現象が発生しています。
何度か実行してもらえれば分かると思うのですが、今回はたまたまデッドロックを検知できたにすぎません。デッドロックが発生せずに終了してしまう場合があるからです。
今回の様な単純なプログラムの場合だと、比較的簡単にデッドロックが発生していることを検知できたのですが、複雑なプログラムになるとなかなか分かりません。なのでデッドロックを解析するのは、非常に困難な作業となります。でも、デッドロックを発生しやすくし、検知しやすくする方法はあります。
上記サンプルコードの「7-2-3.Human.java」のeatメソッドの中を見てください。ここでは、コメントにしてありますが、sleepメソッドを使用しています。これは、ロックを取得した直後に処理を休止させ、デッドロックが発生しやすくするためのタイミングを計っています。
以下に、コメントをはずした場合の実行結果を示します。
■実行結果イメージ
Aさん が、いただきます しました!! Aさん が、[左のお箸] を取得!! Bさん が、いただきます しました!! Bさん が、[右のお箸] を取得!! _
初回でデッドロックが発生しました。この様に、sleepメソッドでタイミングを計ってやることにより、デッドロックを検知しやすくすることが可能です。
※必ず検知できるとは限りません
■デッドロックの発生要因
一般的にデッドロックの発生要因としては、以下の3点が考えられます。
■デッドロックの防止方法
デッドロックの防止方法としては、上記で挙げた3点のうちいづれか1点を解消すればOKです。具体的にどう対応するかについては、以下に示します。
7-3-1 ChopSticks.java
「7-2-1. ChopSticks.java」と同じ
7-3-2. DeadLockTest.java ※赤字の箇所は、「7-2-2.DeadLockTest.java」からの追加・修正点
class DeadLockTest{
public static void main(String args[]){
//お箸を初期化セット
ChopSticks leftStick = new ChopSticks("左のお箸");
ChopSticks rightStick = new ChopSticks("右のお箸");
//左右のお箸をセットにします
StickSet stickSet = new StickSet(leftStick, rightStick);
//Aさん、Bさんのスレッドインスタンスを生成
Human humanA = new Human("Aさん", stickSet);
Human humanB = new Human("Bさん", stickSet);
//スレッドスタート(いっただきまーす)
humanA.start();
humanB.start();
}
}
7-3-3. Human.java ※赤字の箇所は、「7-2-2.DeadLockTest.java」からの追加・修正点
class Human extends Thread{
private final String name;
private final StickSet stickSet;
public Human(String name, StickSet stickSet){
this.name = name;
this.stickSet = stickSet;
}
public void run(){
System.out.println(this.name+" が、いただきます しました!!");
for(int i = 0; i < 10; i++){
try{
//コンビ二弁当を食べる
eat();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(this.name+" が、ごちそうさま しました!!");
}
private void eat() throws InterruptedException{
synchronized(this.stickSet){
//お箸のセットを取得
this.stickSet.getStickSet(this.name);
}
}
}
7-3-4. StickSet.java
class StickSet{ private final ChopSticks leftStick; private final ChopSticks rightStick; public StickSet(ChopSticks leftStick, ChopSticks rightStick){ //お箸をセットで初期設定 this.leftStick = leftStick; this.rightStick = rightStick; } public void getStickSet(String humanName){ //取得したお箸(左)を表示 this.leftStick.getStick(humanName); //取得したお箸(右)を表示 this.rightStick.getStick(humanName); } }
■実行結果イメージ
Aさん が、いただきます しました!!
Aさん が、[左のお箸] を取得!!
Aさん が、[右のお箸] を取得!!
Aさん が、[左のお箸] を取得!!
Aさん が、[右のお箸] を取得!!
Aさん が、[左のお箸] を取得!!
Aさん が、[右のお箸] を取得!!
Aさん が、[左のお箸] を取得!!
Bさん が、いただきます しました!!
Aさん が、[右のお箸] を取得!!
Bさん が、[左のお箸] を取得!!
Bさん が、[右のお箸] を取得!!
Bさん が、[左のお箸] を取得!!
Bさん が、[右のお箸] を取得!!
Bさん が、[左のお箸] を取得!!
Bさん が、[右のお箸] を取得!!
Aさん が、[左のお箸] を取得!!
Aさん が、[右のお箸] を取得!!
Aさん が、[左のお箸] を取得!!
Aさん が、[右のお箸] を取得!!
Aさん が、[左のお箸] を取得!!
Aさん が、[右のお箸] を取得!!
Aさん が、[左のお箸] を取得!!
Aさん が、[右のお箸] を取得!!
Bさん が、[左のお箸] を取得!!
Bさん が、[右のお箸] を取得!!
Bさん が、[左のお箸] を取得!!
Bさん が、[右のお箸] を取得!!
Bさん が、[左のお箸] を取得!!
Bさん が、[右のお箸] を取得!!
Bさん が、[左のお箸] を取得!!
Bさん が、[右のお箸] を取得!!
Aさん が、[左のお箸] を取得!!
Aさん が、[右のお箸] を取得!!
Aさん が、[左のお箸] を取得!!
Aさん が、[右のお箸] を取得!!
Aさん が、ごちそうさま しました!!
Bさん が、[左のお箸] を取得!!
Bさん が、[右のお箸] を取得!!
Bさん が、[左のお箸] を取得!!
Bさん が、[右のお箸] を取得!!
Bさん が、[左のお箸] を取得!!
Bさん が、[右のお箸] を取得!!
Bさん が、ごちそうさま しました!!
デッドロックは発生せず、最後まで処理を進めること(ごちそうさま!!)ができました。
7-3-5. ChopSticks.java
「7-2-1. ChopSticks.java」と同じ
7-3-6. DeadLockTest.java ※赤字の箇所は、「7-2-2.DeadLockTest.java」からの追加・修正点
class DeadLockTest{
public static void main(String args[]){
//お箸を初期化セット
ChopSticks leftStick = new ChopSticks("左のお箸");
ChopSticks rightStick = new ChopSticks("右のお箸");
//Aさん、Bさんのスレッドインスタンスを生成
Human humanA = new Human("Aさん", leftStick, rightStick);
Human humanB = new Human("Bさん", leftStick, rightStick);
//スレッドスタート(いっただきまーす)
humanA.start();
humanB.start();
}
}
7-3-7.
「7-2-3. Human.java」と同じ
■実行結果イメージ
上記の場合と同じく、デッドロックは発生しなくなりました。
実行結果イメージについては、上記と同じなので割愛します。