JAVAマルチスレッド(四)


デッドロック
1、デッドロックとは
簡単に言えば、デッドロックは、システム内のスレッドが互いに占有するリソースを待っているために実行を一時停止し、システムが偽死する現象である.
2、デッドロックはどのように発生したのか
システムには2つの反発リソースAとBがあると仮定し、システム内の2つのスレッド1と2はいずれもAとBを取得してから正常に動作するが、スレッド1はリソースAを先に取り、リソースBをスレッド2はリソースBを先に取り、リソースAをスレッド2は先に取る.これにより、スレッド1がリソースAを先に申請し、リソースBを申請する準備をしている間に、プロセッサスケジューリングによりスレッド2が実行を開始する場合がある.スレッド2は、リソースBを申請し、リソースAを取得する準備をしている間に、リソースAが他のスレッド(1)に占有されていることに気づき、スレッド2が実行を一時停止する.スレッド1が次に実行されると、リソースBが他のスレッド(2)によって占有されていることがわかる.このように,スレッド1と2は互いに占有するリソースを待ち続け,システムが偽死する現象をもたらす.
3、デッドロックの例
次の例では、上記のデッドロック現象を簡単にシミュレートします.
public class Resource
{
    private boolean isTaken = false;
    
    public synchronized void take() throws InterruptedException
    {
        while (isTaken)
            wait();
        
        isTaken = true;
    }
    
    public synchronized void drop()
    {
        isTaken = false;
        
        notifyAll();
    }
    
    public static void main(String[] args) throws IOException
    {
        Resource A = new Resource();
        Resource B = new Resource();
        
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new MyThread(A, B));
        exec.execute(new MyThread(B, A));
        
        System.in.read();
        
        exec.shutdownNow();
    }
}

class MyThread implements Runnable
{
    private static int counter = 0;
    private final int id = counter++;
    
    private Resource first, second;
    
    public MyThread(Resource first, Resource second)
    {
        this.first = first;
        this.second = second;
    }
    
    @Override
    public void run()
    {
        try
        {
            while (!Thread.interrupted())
            {
                first.take();
                Thread.yield();
                second.take();

                System.out.println("Thread " + id + " working...");
                TimeUnit.SECONDS.sleep(1);

                first.drop();
                second.drop();
                Thread.yield();
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

4、デッドロック発生の必要条件
デッドロックの発生には4つの必要条件を満たす必要があります.
  • 反発条件:プロセスが割り当てられたリソースを排他的に使用すること、すなわち、一定期間に1つのプロセスのみがリソースを占有することを指す.この時点で他のプロセスがリソースを要求している場合、リクエスト者は、リソースを占有するプロセスが解放されるまで待つしかありません.
  • 要求および保持条件:プロセスが少なくとも1つのリソースを保持しているが、他のプロセスによって占有されている新しいリソース要求が提案されていることを意味し、要求プロセスはブロックされているが、自分が取得した他のリソースは保持されない.
  • 条件を剥奪しない:プロセスが獲得した資源を指し、使用が完了する前に剥奪されず、使用が完了したときに自分で解放されるしかない.
  • サイクル待ち条件:デッドロックが発生した場合、必然的に1つのプロセス--リソースのリングチェーンが存在し、すなわちプロセス集合{P 0,P 1,P 2,...,Pn}のP 0が1つのP 1が占有するリソースを待っていることを指す.P 1はP 2によって占有されるリソースを待っている、…、PnはP 0によって占有されたリソースを待っている.
  • 5、デッドロックの解決
    デッドロックは4つの必要条件が満たされている場合にのみ発生するため,そのうちの1つを破壊するだけでデッドロックの発生を防止できる.
    上記の例では、最も破壊しやすいのが4番目のループ待ち条件であり、スレッド2もスレッド1のように先にリソースAを申請し、次にリソースBを申請すれば4番目の条件を破壊し、デッドロックの発生を防止することができる.これまでは,上記のmain()メソッドを簡単に修正するだけでよい.
    public static void main(String[] args) throws IOException
    {
        Resource A = new Resource();
        Resource B = new Resource();
            
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new MyThread(A, B));
        exec.execute(new MyThread(A, B));
            
        System.in.read();
            
        exec.shutdownNow();
    }