JAvaデッドロックの例と関連説明


デッドロック
  デッドロックは、複数のスレッドが同時にブロックされ、そのうちの1つまたはすべてがリソースの解放を待っている場合です.スレッドが無期限にブロックされるため、プログラムが正常に終了することはできません.
  デッドロックの原因は、特定のオブジェクトへのスレッドのアクセスを管理するために「synchronized」キーワードを適切に使用しないことです.「synchronized」キーワードの役割は、ある時点で特定のコードブロックを実行できるスレッドが1つしかないことを確保することであり、したがって、実行できるスレッドは、まず変数またはオブジェクトへの排他的なアクセス権を有しなければならない.スレッドがオブジェクトにアクセスすると、スレッドはオブジェクトにロックをかけます.このロックにより、最初のスレッドがオブジェクトにロックを解除するまで、同じオブジェクトにアクセスしたい他のスレッドがブロックされます.
  このため,「synchronized」キーワードを用いると,2つのスレッドが互いに何らかの動作を待つ場合が容易に現れる.コード1は、デッドロックを引き起こす簡単な例です.
//コード1

class Deadlocker {
 int field_1;
 private Object lock_1 = new int[1];
 int field_2;
 private Object lock_2 = new int[1];

 public void method1(int value) {
  “synchronized” (lock_1) {
   “synchronized” (lock_2) {
    field_1 = 0; field_2 = 0;
   }
  }
 }

 public void method2(int value) {
  “synchronized” (lock_2) {
   “synchronized” (lock_1) {
    field_1 = 0; field_2 = 0;
   }
  }
 }
}
 

  参照コード1では、次の手順を考慮します.
  ◆スレッド(ThreadA)がmethod 1()を呼び出す.
  ◆ThreadAはlock_1は同期されますが、優先的に実行できます.
  ◆別のスレッド(ThreadB)が実行を開始する.
  ◆ThreadBはmethod 2()を呼び出す.
  ◆ThreadB獲得lock_2,実行を続行しlock_を取得しようとする1.ただしThreadBはlock_を取得できません1,ThreadAがlock_を占有しているため1.
  ◆現在、ThreadAがlockを解放するのを待っているため、ThreadBがブロックされています.1.
  ◆今度はThreadAが実行を続ける番です.ThreadAはlock_を取得しようとした2、しかし成功することができなくて、lock_のためです2はすでにThreadBに占有されている.
  ◆ThreadAもThreaddBもブロックされ、プログラムがデッドロックしている.
  もちろん、ほとんどのデッドロックは明らかではありません.コードをよく分析する必要があります.規模の大きいマルチスレッドプログラムでは特にそうです.良いスレッド分析ツール、例えばJProbe Threadalyzerは、デッドロックを分析し、問題が発生したコードの位置を指摘することができます.
  ステルスデッドロック
  ステルスデッドロックは、不規範なプログラミング方式によって引き起こされるが、テスト実行のたびにプログラムデッドロックが発生するとは限らない.このため、一部の隠性デッドロックは、アプリケーションが正式にリリースされた後に発見される可能性があるため、通常のデッドロックよりも危害が大きい.次の2つの状況について説明します.ロック順序と占有と待機です.
  ロック順序
  複数の同時スレッドがそれぞれ2つのロックを同時に占有しようとすると、ロック順序が競合する場合があります.あるスレッドが別のスレッドに必要なロックを占有している場合、デッドロックが発生する可能性があります.次の状況を考慮すると、ThreadAとThreadBの2つのスレッドはそれぞれlock_を同時に持つ必要がある.1、lock_2ロックが2つあります.ロックプロセスは次のようになります.
  ◆ThreadA獲得lock_1;
  ◆ThreadAはプリエンプトされ、VMスケジューラはThreadBに移行する.
  ◆ThreadB獲得lock_2;
  ◆ThreadBがプリエンプトされ、VMスケジューラはThreadAに移行する.
  ◆ThreadAはlock_を獲得しようとした2、でもlock_2はThreadBに占有されているので、ThreadAはブロックされています.
  ◆スケジューラはThreadBに移行する;
  ◆ThreadBはlock_を獲得しようとする1だけどlock_1はThreadAに占有されているので、ThreadBがブロックされている.
  ◆ThreadAとThreadBデッドロック.
  コードが少しも変更されていない場合、上記のデッドロックプロセスは発生しない場合があります.VMスケジューラは、いずれかのスレッドにlock_を同時に取得させる可能性があります.1とlock_2 2つのロック、すなわちスレッドが2つのロックを取得するプロセスは中断されていない.この場合、従来のデッドロック検出では、エラーの所在を特定することは困難である.
  占有して待つ
  あるスレッドがロックを取得した後、別のスレッドからの通知を待つ場合、コード2を考慮して別の暗黙的なデッドロックが発生する可能性があります.
//コード2

public class queue {
 static java.lang.Object queueLock_;
 Producer producer_;
 Consumer consumer_;

 public class Producer {
  void produce() {
   while (!done) {
    “synchronized” (queueLock_) {
     produceItemAndAddItToQueue();
     “synchronized” (consumer_) {
      consumer_.notify();
     }
    }
   }
  }

  public class Consumer {
   consume() {
    while (!done) {
     “synchronized” (queueLock_) {
      “synchronized” (consumer_) {
       consumer_.wait();
      }
      removeItemFromQueueAndProcessIt();
     }
    }
   }
  }
 }
}
 

  コード2では、Producerがキューに新しいコンテンツを追加した後、新しいコンテンツを処理するようにConsumerに通知します.問題は、Consumerがキューに追加されたロックを保持し、Producerがキューにアクセスすることを阻止し、ConsumerがProducerの通知を待っている間もロックを維持し続ける可能性があることです.このように、Producerはキューに新しいコンテンツを追加できないのに、ConsumerはProducerが新しいコンテンツに追加されるという通知を待っているため、結果としてデッドロックになる.
  待機中に占有されるロックは、理想的な状況に応じて発展する可能性があるため、Consumerに占有されるロックを必要としないため、隠れたデッドロックです.それでも、Producerスレッドがロックを必要としないと確信する絶対的に信頼できる理由がない限り、このプログラミング方式は安全ではありません.「占有および待機」は、スレッドAがスレッドBに必要なロックおよび待機を占有し、スレッドBがスレッドCに必要なロックおよび待機を占有するなど、一連のスレッド待機を引き起こすこともある.
  コード2のエラーを修正するには、Consumerクラスを修正し、wait()をsynchronized()に移動するだけです.
  したがって、デッドロックを回避する一般的な経験則は、いくつかのスレッドが共有リソースA、B、Cにアクセスする場合、各スレッドが同じ順序でアクセスすることを保証することであり、例えば、先にAにアクセスし、BとCにアクセスすることである.
  また、Thread類のsuspend()法もデッドロックを招きやすいため、この方法は廃棄する.