デッドロック
日本語 | 死鍵、膠着状態 |
英語 | deadlock |
ふりがな | でっどろっく |
フリガナ | デッドロック |
マルチスレッドにおいて、複数のスレッドが互いのスレッドを排他してしまいプログラムが停止すること。
複数のスレッドが、synchronizedブロック等によって複数の変数を対象に排他処理を行う場合に発生する。
単純な例としては、スレッドAとスレッドB、ロック用変数1とロック用変数2がある場合に、以下のように処理を行うとデッドロックする。
1. スレッドAが変数1をロックする。
2. スレッドBが変数2をロックする。
3. スレッドAが変数2をロックしようとしたらすでにされていたので一時停止。
4. スレッドBが変数1をロックしようとしたらすでにされていたので一時停止。
このように、同期を取るメソッドそれぞれにおいて、複数のロック用変数の「ロック順」が異なる場合に発生する。スレッドAが「変数1→変数2」の順でロックし、スレッドBが「変数2→変数1」の順でロックするため、同時にロックが掛かると停止してしまう。
実際には、このように分かりやすくはない。
順番にロックを掛ける際に、ロックの間隔が非常に短い場合、ほぼ同時にロックが掛かるためである。
スレッドAが変数1をロックし、その後変数2をロックする前にスレッドBが変数2をロックしてしまうために発生するのであって、スレッドAが変数1と変数2をほぼ同時にロックしてしまえば、スレッドBの方が変数2のロック解除を待つことになるからである。
問題は、CPUの処理順等によって、「確率的」に発生してしまう可能性があるためである。
CPUの処理順の変更や、他のアプリケーションの影響、OSの状態等によって、ロックの間隔が空いてしまい、他のスレッドが先にロックを掛けてしまうと、デッドロックが発生してしまう。
これらの要因は常に発生するわけではないため、入念にテストを行わないと発見できない。また、発見できても原因の特定や解決そのものが難しいことも多い。
デッドロックは、ロック用の変数がひとつであれば発生しないため、そういったシンプルなプログラムを組むよう心がけることが重要だろう。
複数のスレッドが、synchronizedブロック等によって複数の変数を対象に排他処理を行う場合に発生する。
単純な例としては、スレッドAとスレッドB、ロック用変数1とロック用変数2がある場合に、以下のように処理を行うとデッドロックする。
1. スレッドAが変数1をロックする。
2. スレッドBが変数2をロックする。
3. スレッドAが変数2をロックしようとしたらすでにされていたので一時停止。
4. スレッドBが変数1をロックしようとしたらすでにされていたので一時停止。
このように、同期を取るメソッドそれぞれにおいて、複数のロック用変数の「ロック順」が異なる場合に発生する。スレッドAが「変数1→変数2」の順でロックし、スレッドBが「変数2→変数1」の順でロックするため、同時にロックが掛かると停止してしまう。
実際には、このように分かりやすくはない。
順番にロックを掛ける際に、ロックの間隔が非常に短い場合、ほぼ同時にロックが掛かるためである。
スレッドAが変数1をロックし、その後変数2をロックする前にスレッドBが変数2をロックしてしまうために発生するのであって、スレッドAが変数1と変数2をほぼ同時にロックしてしまえば、スレッドBの方が変数2のロック解除を待つことになるからである。
問題は、CPUの処理順等によって、「確率的」に発生してしまう可能性があるためである。
CPUの処理順の変更や、他のアプリケーションの影響、OSの状態等によって、ロックの間隔が空いてしまい、他のスレッドが先にロックを掛けてしまうと、デッドロックが発生してしまう。
これらの要因は常に発生するわけではないため、入念にテストを行わないと発見できない。また、発見できても原因の特定や解決そのものが難しいことも多い。
デッドロックは、ロック用の変数がひとつであれば発生しないため、そういったシンプルなプログラムを組むよう心がけることが重要だろう。
参考サイト
// Sample.java
public class Sample
{
public static void main( String[] args )
{
try
{
// データクラス。
DataClass data1 = new DataClass();
DataClass data2 = new DataClass();
// 別スレッドの方を呼び出します。
OtherThread thread = new OtherThread( data1, data2 );
thread.start();
// 2秒待ちます。OtherThreadの方を先に
// 実行するためです。
Thread.sleep( 2 * 1000 );
// こちらでも。
System.out.println( "Sample開始" );
// もうひとつのsynchronizedブロックを持つメソッドを呼び出します。
SynchronizeClass synchronizeClass = new SynchronizeClass();
synchronizeClass.methodHasSynchronizedBlockB( "Sample", data1, data2 );
System.out.println( "Sample終了" );
// OtherThread開始
// A : data1をロックします
// A : data1ロック開始
// Sample開始
// B : data2をロックします
// B : data2ロック開始
// A : data2をロックします
// B : data1をロックします
// ここでデッドロックが発生します。
// 「ロックします」のところで、すでにロックが掛かっていると一時停止します。
// そのため、
//
// ・Aがdata1をロックする。
// ・Bがdata2をロックする。
// ・Aがdata2をロックしようとしたらすでにされていたので一時停止。
// ・Bがdata1をロックしようとしたらすでにされていたので一時停止。
//
// となり、デッドロックが発生します。
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
}
/**
* 別スレッドとして実行するためのクラス。
*/
class OtherThread extends Thread
{
/** データクラス。 */
private DataClass data1;
private DataClass data2;
/** コンストラクタ。 */
public OtherThread( DataClass data1, DataClass data2 )
{
this.data1 = data1;
this.data2 = data2;
}
/**
* Threadクラスのrun()メソッドを
* オーバーライドしたメソッド。このメソッドが
* 別スレッドとして呼び出されます。
*/
public void run()
{
System.out.println( "OtherThread開始" );
// synchronizedブロックを持つメソッドを呼び出します。
SynchronizeClass synchronizeClass = new SynchronizeClass();
synchronizeClass.methodHasSynchronizedBlockA( "OtherThread", data1, data2 );
System.out.println( "OtherThread終了" );
}
}
/**
* 同期処理テスト用クラス。
*/
class SynchronizeClass
{
/**
* synchronizedブロックを持つメソッド
*/
public void methodHasSynchronizedBlockA( String name, DataClass data1, DataClass data2 )
{
try
{
System.out.println( "A : data1をロックします" );
synchronized( data1 )
{
System.out.println( "A : data1ロック開始" );
// 5秒待ちます。
Thread.sleep( 5 * 1000 );
System.out.println( "A : data2をロックします" );
synchronized( data2 )
{
System.out.println( "A : data2ロック開始" );
System.out.println( "A : data2ロック終了" );
}
System.out.println( "A : data2のロックを解放しました" );
System.out.println( "A : data1ロック終了" );
}
System.out.println( "A : data1のロックを解放しました" );
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
/**
* もうひとつのsynchronizedブロックを持つメソッド
*/
public void methodHasSynchronizedBlockB( String name, DataClass data1, DataClass data2 )
{
try
{
System.out.println( "B : data2をロックします" );
synchronized( data2 )
{
System.out.println( "B : data2ロック開始" );
// 5秒待ちます。
Thread.sleep( 5 * 1000 );
System.out.println( "B : data1をロックします" );
synchronized( data1 )
{
System.out.println( "B : data1ロック開始" );
System.out.println( "B : data1ロック終了" );
}
System.out.println( "B : data1のロックを解放しました" );
System.out.println( "B : data2ロック終了" );
}
System.out.println( "B : data2のロックを解放しました" );
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
}
/**
* データクラス。
*/
class DataClass
{
}
public class Sample
{
public static void main( String[] args )
{
try
{
// データクラス。
DataClass data1 = new DataClass();
DataClass data2 = new DataClass();
// 別スレッドの方を呼び出します。
OtherThread thread = new OtherThread( data1, data2 );
thread.start();
// 2秒待ちます。OtherThreadの方を先に
// 実行するためです。
Thread.sleep( 2 * 1000 );
// こちらでも。
System.out.println( "Sample開始" );
// もうひとつのsynchronizedブロックを持つメソッドを呼び出します。
SynchronizeClass synchronizeClass = new SynchronizeClass();
synchronizeClass.methodHasSynchronizedBlockB( "Sample", data1, data2 );
System.out.println( "Sample終了" );
// OtherThread開始
// A : data1をロックします
// A : data1ロック開始
// Sample開始
// B : data2をロックします
// B : data2ロック開始
// A : data2をロックします
// B : data1をロックします
// ここでデッドロックが発生します。
// 「ロックします」のところで、すでにロックが掛かっていると一時停止します。
// そのため、
//
// ・Aがdata1をロックする。
// ・Bがdata2をロックする。
// ・Aがdata2をロックしようとしたらすでにされていたので一時停止。
// ・Bがdata1をロックしようとしたらすでにされていたので一時停止。
//
// となり、デッドロックが発生します。
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
}
/**
* 別スレッドとして実行するためのクラス。
*/
class OtherThread extends Thread
{
/** データクラス。 */
private DataClass data1;
private DataClass data2;
/** コンストラクタ。 */
public OtherThread( DataClass data1, DataClass data2 )
{
this.data1 = data1;
this.data2 = data2;
}
/**
* Threadクラスのrun()メソッドを
* オーバーライドしたメソッド。このメソッドが
* 別スレッドとして呼び出されます。
*/
public void run()
{
System.out.println( "OtherThread開始" );
// synchronizedブロックを持つメソッドを呼び出します。
SynchronizeClass synchronizeClass = new SynchronizeClass();
synchronizeClass.methodHasSynchronizedBlockA( "OtherThread", data1, data2 );
System.out.println( "OtherThread終了" );
}
}
/**
* 同期処理テスト用クラス。
*/
class SynchronizeClass
{
/**
* synchronizedブロックを持つメソッド
*/
public void methodHasSynchronizedBlockA( String name, DataClass data1, DataClass data2 )
{
try
{
System.out.println( "A : data1をロックします" );
synchronized( data1 )
{
System.out.println( "A : data1ロック開始" );
// 5秒待ちます。
Thread.sleep( 5 * 1000 );
System.out.println( "A : data2をロックします" );
synchronized( data2 )
{
System.out.println( "A : data2ロック開始" );
System.out.println( "A : data2ロック終了" );
}
System.out.println( "A : data2のロックを解放しました" );
System.out.println( "A : data1ロック終了" );
}
System.out.println( "A : data1のロックを解放しました" );
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
/**
* もうひとつのsynchronizedブロックを持つメソッド
*/
public void methodHasSynchronizedBlockB( String name, DataClass data1, DataClass data2 )
{
try
{
System.out.println( "B : data2をロックします" );
synchronized( data2 )
{
System.out.println( "B : data2ロック開始" );
// 5秒待ちます。
Thread.sleep( 5 * 1000 );
System.out.println( "B : data1をロックします" );
synchronized( data1 )
{
System.out.println( "B : data1ロック開始" );
System.out.println( "B : data1ロック終了" );
}
System.out.println( "B : data1のロックを解放しました" );
System.out.println( "B : data2ロック終了" );
}
System.out.println( "B : data2のロックを解放しました" );
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
}
/**
* データクラス。
*/
class DataClass
{
}
// Sample.java public class Sample { public static void main( String[] args ) { try { // データクラス。 DataClass data1 = new DataClass(); DataClass data2 = new DataClass(); // 別スレッドの方を呼び出します。 OtherThread thread = new OtherThread( data1, data2 ); thread.start(); // 2秒待ちます。OtherThreadの方を先に // 実行するためです。 Thread.sleep( 2 * 1000 ); // こちらでも。 System.out.println( "Sample開始" ); // もうひとつのsynchronizedブロックを持つメソッドを呼び出します。 SynchronizeClass synchronizeClass = new SynchronizeClass(); synchronizeClass.methodHasSynchronizedBlockB( "Sample", data1, data2 ); System.out.println( "Sample終了" ); // OtherThread開始 // A : data1をロックします // A : data1ロック開始 // Sample開始 // B : data2をロックします // B : data2ロック開始 // A : data2をロックします // B : data1をロックします // ここでデッドロックが発生します。 // 「ロックします」のところで、すでにロックが掛かっていると一時停止します。 // そのため、 // // ・Aがdata1をロックする。 // ・Bがdata2をロックする。 // ・Aがdata2をロックしようとしたらすでにされていたので一時停止。 // ・Bがdata1をロックしようとしたらすでにされていたので一時停止。 // // となり、デッドロックが発生します。 } catch( InterruptedException e ) { // sleep()メソッドが途中で中断されると // InterruptedException例外が投げられます。 // 滅多にないですが。 e.printStackTrace(); } } } /** * 別スレッドとして実行するためのクラス。 */ class OtherThread extends Thread { /** データクラス。 */ private DataClass data1; private DataClass data2; /** コンストラクタ。 */ public OtherThread( DataClass data1, DataClass data2 ) { this.data1 = data1; this.data2 = data2; } /** * Threadクラスのrun()メソッドを * オーバーライドしたメソッド。このメソッドが * 別スレッドとして呼び出されます。 */ public void run() { System.out.println( "OtherThread開始" ); // synchronizedブロックを持つメソッドを呼び出します。 SynchronizeClass synchronizeClass = new SynchronizeClass(); synchronizeClass.methodHasSynchronizedBlockA( "OtherThread", data1, data2 ); System.out.println( "OtherThread終了" ); } } /** * 同期処理テスト用クラス。 */ class SynchronizeClass { /** * synchronizedブロックを持つメソッド */ public void methodHasSynchronizedBlockA( String name, DataClass data1, DataClass data2 ) { try { System.out.println( "A : data1をロックします" ); synchronized( data1 ) { System.out.println( "A : data1ロック開始" ); // 5秒待ちます。 Thread.sleep( 5 * 1000 ); System.out.println( "A : data2をロックします" ); synchronized( data2 ) { System.out.println( "A : data2ロック開始" ); System.out.println( "A : data2ロック終了" ); } System.out.println( "A : data2のロックを解放しました" ); System.out.println( "A : data1ロック終了" ); } System.out.println( "A : data1のロックを解放しました" ); } catch( InterruptedException e ) { // sleep()メソッドが途中で中断されると // InterruptedException例外が投げられます。 // 滅多にないですが。 e.printStackTrace(); } } /** * もうひとつのsynchronizedブロックを持つメソッド */ public void methodHasSynchronizedBlockB( String name, DataClass data1, DataClass data2 ) { try { System.out.println( "B : data2をロックします" ); synchronized( data2 ) { System.out.println( "B : data2ロック開始" ); // 5秒待ちます。 Thread.sleep( 5 * 1000 ); System.out.println( "B : data1をロックします" ); synchronized( data1 ) { System.out.println( "B : data1ロック開始" ); System.out.println( "B : data1ロック終了" ); } System.out.println( "B : data1のロックを解放しました" ); System.out.println( "B : data2ロック終了" ); } System.out.println( "B : data2のロックを解放しました" ); } catch( InterruptedException e ) { // sleep()メソッドが途中で中断されると // InterruptedException例外が投げられます。 // 滅多にないですが。 e.printStackTrace(); } } } /** * データクラス。 */ class DataClass { }