JAvaマルチスレッドデッドロック


原文リンク作者:Jakob Jenkov訳者:申章校正:丁一
JAvaにおけるデッドロックは、2つ以上のスレッドが、他のデッドロック状態にあるスレッドが保持するロックをブロックしている.デッドロックは、通常、複数のスレッドが同時に発生するが、異なる順序で同じロックのセットを要求する場合に発生する.
例えば、スレッド1がAをロックし、Bをロックしようとすると、スレッド2がBをロックし、Aをロックしようとすると、デッドロックが発生する.スレッド1は永遠にBを得ることができず、スレッド2も永遠にAを得ることができず、このようなことが起こったことを永遠に知らない.互いのオブジェクト(AとB)を得るために、それらは永遠にブロックされます.この場合、デッドロックです.
次のようになります.
Thread 1  locks A, waits for B
Thread 2  locks B, waits for A
ここには、異なるインスタンスのsynchronizedメソッドを呼び出すTreeNodeクラスの例があります.
public class TreeNode {
        TreeNode parent   = null;  
        List children = new ArrayList();
        public synchronized void addChild(TreeNode child){
                if(!this.children.contains(child)) {
                        this.children.add(child);
                        child.setParentOnly(this);
                }
        }
  
        public synchronized void addChildOnly(TreeNode child){
                if(!this.children.contains(child){
                        this.children.add(child);
                }
        }
  
        public synchronized void setParent(TreeNode parent){
                this.parent = parent;
                parent.addChildOnly(this);
        }
        public synchronized void setParentOnly(TreeNode parent){
                this.parent = parent;
        }
}
スレッド1がparentを呼び出すと.addChild(child)メソッドと同時に別のスレッド2がchildを呼び出す.setParent(parent)メソッドでは、2つのスレッドのparentが同じオブジェクトを表し、childも同様で、デッドロックが発生します.次の疑似コードは、このプロセスを説明します.
Thread 1: parent.addChild(child);//locks parent
          --> child.setParentOnly(parent);
Thread 2: child.setParent(parent);//locks child
          --> parent.addChildOnly()
まずスレッド1がparentを呼び出す.addChild(child).addChild()は同期されるため、スレッド1はparentオブジェクトにロックをかけ、他のスレッドがオブジェクトにアクセスしないようにします.
スレッド2はchildを呼び出す.setParent(parent).setParent()は同期されるため、スレッド2はchildオブジェクトにロックをかけ、他のスレッドがオブジェクトにアクセスしないようにします.
childオブジェクトとparentオブジェクトは2つの異なるスレッドにロックされました.次にスレッド1はchildを呼び出そうとする.setParentOnly()メソッドですが、childオブジェクトはスレッド2でロックされているため、呼び出しがブロックされます.スレッド2もparentを呼び出そうとする.addChildOnly()ですが、parentオブジェクトがスレッド1によってロックされているため、スレッド2もこのメソッドでブロックされています.2つのスレッドがブロックされ、別のスレッドが持つロックの取得を待っています.
注意:上記のように、この2つのスレッドはparentを同時に呼び出す必要がある.addChild(child)とchild.setParent(parent)メソッドは、同じparentオブジェクトと同じchildオブジェクトであるため、デッドロックが発生する可能性があります.上のコードは、デッドロックが発生するまでしばらく実行される可能性があります.
これらのスレッドは同時にロックを取得する必要があります.たとえば、スレッド1がスレッド2を少しリードしてAとBの2つのオブジェクトをロックすることに成功すると、スレッド2はBにロックをかけようとするとブロックされ、デッドロックは発生しません.スレッドスケジューリングは通常予測不可能であるため、デッドロックがいつ発生するかを正確に予測する方法はありません.ただ発生する可能性があります.
より複雑なデッドロック
デッドロックには2つのスレッドだけが含まれていない可能性があります.これにより、デッドロックの検出がさらに困難になります.次に、4つのスレッドでデッドロックが発生した例を示します.
Thread 1  locks A, waits for B 
   
Thread 2  locks B, waits for C
Thread 3  locks C, waits for D
Thread 4  locks D, waits for A

スレッド1はスレッド2を待ち、スレッド2はスレッド3を待ち、スレッド3はスレッド4を待ち、スレッド4はスレッド1を待つ.
データベースのデッドロック
より複雑なデッドロックシーンは、データベーストランザクションで発生します.1つのデータベース・トランザクションは、複数のSQL更新要求から構成される場合があります.1つのトランザクションでレコードを更新すると、このレコードはロックされ、最初のトランザクションが終了するまで他のトランザクションの更新要求を回避します.同じトランザクションの更新リクエストごとにレコードがロックされる可能性があります.
複数のトランザクションが同時に同じレコードを更新する必要がある場合、デッドロックが発生する可能性があります.たとえば、次のようにします.
Transaction 1, request 1, locks record 1 for updateTransaction 2, request 1, locks record 2 for updateTransaction 1, request 2, tries to lock record 2 for update.Transaction 2, request 2, tries to lock record 1 for update.
ロックは異なるリクエストで発生し、1つのトランザクションに必要なすべてのロックを事前に知ることはできないため、データベース・トランザクションのデッドロックの検出と回避は困難です.