Java サンプル - デッドロックとその解決方法
デッドロックとは、複数のスレッドが同時にブロックされ、そのうちの1つまたはすべてが特定のリソースの解放を待っている状態のことです。スレッドが無期限にブロックされるため、プログラムは正常に終了できません。
java でデッドロックが発生するための4つの必要条件:
- 1、相互排他使用、すなわちリソースが1つのスレッドによって使用(占有)されているとき、他のスレッドは使用できない
- 2、横取り不可、リソース要求者はリソース占有者から強制的にリソースを奪うことはできず、リソースはリソース占有者が自発的に解放することしかできない。
- 3、要求と保持、すなわちリソース要求者が他のリソースを要求すると同時に、元のリソースの占有を維持すること。
- 4、循環待ち、すなわち待機キューが存在する:P1がP2のリソースを占有し、P2がP3のリソースを占有し、P3がP1のリソースを占有する。これにより待機ループが形成される。
上記の4つの条件がすべて成立すると、デッドロックが発生します。もちろん、デッドロックの状態でも上記のいずれかの条件を壊せば、デッドロックを解消できます。次にjavaコードでデッドロックの発生をシミュレートしてみます。
デッドロック問題を解決する方法は、1つはsynchronizedを使用する方法、もう1つはLock明示的ロックを使用する方法です。
しかし、ロックを不適切に使用し、同時に複数のオブジェクトをロックする必要がある場合は、次のようにデッドロックが発生します:
LockTest.java ファイル
import java.util.Date;
public class LockTest {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args) {
LockA la = new LockA();
new Thread(la).start();
LockB lb = new LockB();
new Thread(lb).start();
}
}
class LockA implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + "LockA 実行開始");
while(true){
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + "LockA が obj1 をロック");
Thread.sleep(3000); //ここでの待機はBにロックする機会を与えるため
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + "LockA が obj2 をロック");
Thread.sleep(60 * 1000); //テストのため、占有したら解放しない
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockB implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + "LockB 実行開始");
while(true){
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + "LockB が obj2 をロック");
Thread.sleep(3000); //ここでの待機はAにロックする機会を与えるため
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + "LockB が obj1 をロック");
Thread.sleep(60 * 1000); //テストのため、占有したら解放しない
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
上記コードの実行出力結果は次のとおりです:
Tue May 05 10:51:06 CST 2015 LockB 开始执行 Tue May 05 10:51:06 CST 2015 LockA 开始执行 Tue May 05 10:51:06 CST 2015 LockB 锁住 obj2 Tue May 05 10:51:06 CST 2015 LockA 锁住 obj1
このときデッドロックが発生します。
この問題を解決するために、明示的なロックは使用せず、セマフォを使って制御します。
セマフォはリソースにアクセスできるスレッド数を制御できます。ここでは1つのスレッドだけがアクセスできるように指定することで、ロックに似た動作を実現します。また、セマフォは取得のタイムアウト時間を指定できるため、このタイムアウト時間に基づいて追加の処理を行うことができます。
取得に成功しない場合は、一般的には再試行するか、試行回数を指定するか、すぐに終了することもできます。
以下のコードを見てみましょう:
UnLockTest.java ファイル
import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class UnLockTest {
public static String obj1 = "obj1";
public static final Semaphore a1 = new Semaphore(1);
public static String obj2 = "obj2";
public static final Semaphore a2 = new Semaphore(1);
public static void main(String[] args) {
LockAa la = new LockAa();
new Thread(la).start();
LockBb lb = new LockBb();
new Thread(lb).start();
}
}
class LockAa implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() + "LockA 実行開始");
while (true) {
if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + "LockA が obj1 をロック");
if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + "LockA が obj2 をロック");
Thread.sleep(60 * 1000); // do something
}else{
System.out.println(new Date().toString() + "LockA が obj2 のロックに失敗");
}
}else{
System.out.println(new Date().toString() + "LockA が obj1 のロックに失敗");
}
UnLockTest.a1.release(); //解放
UnLockTest.a2.release();
Thread.sleep(1000); //すぐに再試行します。現実の状況では、do something は不確定です
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockBb implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() + "LockB 実行開始");
while (true) {
if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + "LockB が obj2 をロック");
if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + "LockB が obj1 をロック");
Thread.sleep(60 * 1000); // do something
}else{
System.out.println(new Date().toString() + "LockB が obj1 のロックに失敗");
}
}else{
System.out.println(new Date().toString() + "LockB が obj2 のロックに失敗");
}
UnLockTest.a1.release(); //解放
UnLockTest.a2.release();
Thread.sleep(10 * 1000); //ここではデモンストレーションのためだけに、tryAcquireは1秒だけ使用し、BはAに実行時間を譲る必要があります。そうしないと、二者は永遠にデッドロックになります
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
上記サンプルコードの出力結果は次のとおりです:
Tue May 05 10:59:13 CST 2015 LockA 开始执行 Tue May 05 10:59:13 CST 2015 LockB 开始执行 Tue May 05 10:59:13 CST 2015 LockB 锁住 obj2 Tue May 05 10:59:13 CST 2015 LockA 锁住 obj1 Tue May 05 10:59:14 CST 2015LockB 锁 obj1 失败 Tue May 05 10:59:14 CST 2015LockA 锁 obj2 失败 Tue May 05 10:59:15 CST 2015 LockA 锁住 obj1 Tue May 05 10:59:15 CST 2015 LockA 锁住 obj2その他の拡張
Java サンプル