Javaスレッドのデッドロックとその解決策

スレッドのデッドロックを理解するには、最初にデッドロックとは何かを理解する必要があります

 

デッドロック

素人の言葉で言うと、デッドロックとは、リソースの競合や、実行中の2つ以上のプロセスやスレッド間の通信によって引き起こされるブロッキング現象であり、外力がないと前進できません。

 

もっと簡単な例を使ってみましょう

 

たとえば、この交通渋滞の例では、図から4方向に走行する車が互いにブロックしていることがわかります。1方向に戻る車がない場合、デッドロックが発生します。

上の図でデッドロックが発生する理由は4つあります。

1.相互に排他的な条件:リソースは、一度に1つのスレッドでのみ使用できます。マップ上の各道路は一方向の車しか通過できないため、デッドロックの条件の1つが満たされています

2.要求と保留の条件:リソースを要求することによってプロセスがブロックされると、プロセスは取得したリソースを保持し続けます。マップ上の各方向の車が他の方向の車が避難するのを待っていることがわかります。したがって、デッドロックの2番目の条件が満たされています。

3.非剥奪条件:プロセスによって取得されたリソースは、使い果たされる前に強制的に剥奪することはできません。ここでは、交通警察がなく、他の方向への車の避難を強制的に要求することはできないと想定されているため、デッドロックの3番目の条件が満たされます。

4.循環待機条件:ある種の循環待機リソース関係が、複数のプロセスまたはスレッド間で形成されます。これは非常に直感的に図に表されています
 

 

デッドロックJavaコードの小さな例

 

 

 

package huaxin2016_9_9;

public class ThreadDeadlock {
	public static void main(String[] args) throws InterruptedException {
		Object obj1 = new Object();
		Object obj2 = new Object();
		Object obj3 = new Object(); 
		//新建三个线程
		Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1"); 
		Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2"); 
		Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3"); 
		//让线程依次开始
		t1.start(); 
		//让线程休眠
		Thread.sleep(5000);
		t2.start();
		Thread.sleep(5000);
		t3.start();
	} 
} 
class SyncThread implements Runnable{ 
	private Object obj1; 
	private Object obj2;
	//构造函数
	public SyncThread(Object o1, Object o2){ 
		this.obj1=o1;
		this.obj2=o2;
	} 
	@Override
	public void run() {
		//获取并当前运行线程的名称
		String name = Thread.currentThread().getName();
		System.out.println(name + " acquiring lock on "+obj1);
		
		
		synchronized (obj1) { 
			System.out.println(name + " acquired lock on "+obj1); 
			work();
			System.out.println(name + " acquiring lock on "+obj2); 
			synchronized (obj2) { 
				System.out.println(name + " acquired lock on "+obj2); 
				work();
		    } 
			System.out.println(name + " released lock on "+obj2); 
	    } 
		System.out.println(name + " released lock on "+obj1); 
		System.out.println(name + " finished execution.");
	}
	private void work() { 
		try { 
			Thread.sleep(30000); 
		} 
		catch (InterruptedException e) { 
			e.printStackTrace();
		}
	} 
}

上記のデッドロックの小さな例の結果は次のとおりです。

 

 

t1 acquiring lock on java.lang.Object@675d5ed4
t1 acquired lock on java.lang.Object@675d5ed4
t2 acquiring lock on java.lang.Object@7943f708
t2 acquired lock on java.lang.Object@7943f708
t3 acquiring lock on java.lang.Object@46767615
t3 acquired lock on java.lang.Object@46767615
t1 acquiring lock on java.lang.Object@7943f708
t2 acquiring lock on java.lang.Object@46767615
t3 acquiring lock on java.lang.Object@675d5ed4

t1、t2、およびt3はすべてリソースを要求していることが直感的にわかりますが、それらはすべて独自のリソースを維持しているため、デッドロックが発生します。

 

 

解決:

1.相互に排他的な条件を破るには、プロセスが特定のリソースに同時にアクセスできるようにする必要があります。この方法は実際のシナリオの対象であり、条件を達成するのは簡単ではありません。

2.非プリエンプション条件を解除します。このように、プロセスが占有者から特定のリソースを強制的に取得できるようにするか、リソースを占有するプロセスが他のリソースの占有に適用できなくなることを単に理解する必要があります。アプリケーションは、手元にあるリソースの後にリリースする必要があります。これは実際には、適切なシナリオを見つけることも困難です。

3.プロセスは、実行前にすべてのリソースに適用されます。そうでない場合、プロセスは実行準備完了状態に入ることができません。この方法は便利なようですが、その欠点は、リソース使用率の低下とプロセスの同時実行性につながる可能性があることです。

4.リソースアプリケーションのループを回避します。つまり、事前にリソースを並べ替えて番号を付け、番号に従って割り当てます。この方法は、リソース使用率とシステムスループットを効果的に向上させることができますが、システムオーバーヘッドが増加し、プロセスがリソースを消費する時間が長くなります。

 

(1)最も単純で最も一般的に使用される方法はシステムを再起動することですが、この方法は非常にコストがかかるため、プロセスによって完了したすべての計算作業が、死亡への参加を含めて無駄になります。ロックされ、デッドロックに関与していないプロセス。

(2)プロセスをキャンセルし、リソースを奪います。デッドロックに関与しているプロセスを終了し、それらが占有しているリソースを回復して、デッドロックを取り除きます。現時点では、デッドロックに参加しているすべてのプロセスを一度にキャンセルしてすべてのリソースを奪うか、デッドロックに参加しているプロセスを徐々にキャンセルして、デッドロックプロセスによって占有されているリソースを徐々に回復するかの2つの状況があります。一般的に、段階的にキャンセルするプロセスを選択するときは、特定の原則に従う必要があります。目的は、プロセスの優先度に従ってプロセスのコストを決定するなど、最小のコストでプロセスをキャンセルすることです。実行中のプロセスとプロセスに関連する外部ジョブのコストなどの要因;

(3)プロセスのロールバック戦略、つまり、デッドロックに参加しているプロセスをデッドロックの前のポイントにロールバックさせ、この時点で実行を継続し、デッドロックが再発しないようにします。これは理想的な方法ですが、操作に非常にコストがかかります。将来のロールバックに備えて、プロセスのすべての変更を記録するスタックなどのメカニズムが必要です。これが不可能な場合もあります。

 

 

 

おすすめ

転載: blog.csdn.net/dream_18/article/details/52644903
おすすめ