『JAVA同時プログラミング実戦』第十章活発性の危険を避ける

10248 ワード

10.1デッドロック
誰もが他の人に必要なリソースを持ちたいと思っています.同時に、他の人がすでに持っているリソースを待っています.そして、すべての人が必要なリソースを取得する前に、すでに持っているリソースを放棄しません.
  • 過度にロックを使用すると、ロック順序のデッドロック(Lock-Ordering Deadlock)
  • を引き起こす可能性がある.
  • スレッドプールと信号量を使用するリソースの使用を制限するなどの制限動作は、リソースデッドロック
  • を引き起こす可能性がある.
  • データベースシステムの設計では、デッドロックのモニタリングとデッドロックからのリカバリを考慮しています.犠牲者を選択し、それを放棄します.強制終了されたものを再実行することができます.
  • JVMはデッドロック問題を解決する上でDBほど強くなく、スレッドは永遠に使用できません.
  • デッドロックが発生すると、最悪の場合、高負荷の場合が多い.

  • 10.1.1ロック順序デッドロック
    理由:2つのスレッドは、同じロックを異なる順序で取得しようとしています.
    プログラムリスト10-1簡単なロック順序デッドロック(そうしないでください)
    //  ,      
    public class LeftRightDeadlock {
        private final Object left = new Object();
        private final Object right = new Object();
        
        public void leftRight() {
            synchronized (left) {
                synchronized (right) {
                    //TODO doSomething();
                }
            }
        }
        
        public void rightLeft() {
            synchronized (right) {
                synchronized (left) {
                    //TODO doSomething();
                }
            }
        }
    }
    

    10.1.2動的ロック順序デッドロック
    プログラムリスト10-2動的ロックシーケンスデッドロック(そうしないでください)
    Aスレッド:transferMoney(myAccount,yourAccount,10);Bスレッド:transferMoney(yourAccount,myAccount,20);
    public class DynamicOrderDeadlock {
        public void transferMoney(Account fromAccount, 
                                  Account toAccount, 
                                  DollarAmount amount) 
                throws InsufficientResourcesException {
            synchronized (fromAccount) {
                synchronized (toAccount) {
                    if(fromAccount.getBalance().compareTo(amount) < 0) {
                        throw new InsufficientResourcesException();
                    } else {
                        fromAccount.debit(amount);
                        toAccount.credit(amount);
                    }
                }
            }
        }
    }
    

    プログラムリスト10-3は、順序によってデッドロックを回避する
    //    10-3           
        private static final Object tieLock = new Object();
    
        public void transferMoney2(Account fromAccount, Account toAccount, DollarAmount amount)
                throws InsufficientResourcesException {
            //----------              ----------
            class Helper {
                public void transferMoney2() throws InsufficientResourcesException {
                    if (fromAccount.getBalance().compareTo(amount) < 0) {
                        throw new InsufficientResourcesException();
                    } else {
                        fromAccount.debit(amount);
                        toAccount.credit(amount);
                    }
                }
            }
            
            //---------            -----------
            
            int fromHash = System.identityHashCode(fromAccount);
            int toHash = System.identityHashCode(toAccount);
            //            ,            
            if(fromHash < toHash)
                synchronized (fromAccount) {
                    synchronized (toAccount) {
                        new Helper().transferMoney2();
                    }
                }
            else if(fromHash > toHash)
                synchronized (toAccount) {
                    synchronized (fromAccount) {
                        new Helper().transferMoney2();
                    }
                }
            else if(fromHash == toHash) //       ,2            
                /*
                 *    (TieBreaking[    ]) ,                    2  
                 *           ,           
                 *   System.identityHashCode            ,
                 *             ,         
                 */
                synchronized (tieLock) {
                    synchronized (fromAccount) {
                        synchronized (toAccount) {
                            new Helper().transferMoney2();
                        }
                    }
                }
        }
    

    プログラムリスト10-4は典型的な条件の下でデッドロックのループが発生します???分からなかった!!
    public class DemonstrateDeadlock {
        private static final int NUM_THREADS = 20;
        private static final int NUM_ACCOUNTS = 5;
        private static final int NUM_ITERATIONS = 100_0000;
        
        public static void main(String[] args) {
            final Random rnd = new Random();
            final Account[] accounts = new Account[NUM_ACCOUNTS];
            
            for (int i = 0; i < accounts.length; i++)
                accounts[i] = new Account();
            
            class TransferThread extends Thread {
                @Override
                public void run() {
                    for (int i = 0; i < NUM_ITERATIONS; i++) {
                        int fromAcct = rnd.nextInt(NUM_ACCOUNTS);
                        int toAcct = rnd.nextInt(NUM_ACCOUNTS);
                        DollarAmount amount = new DollarAmount(new BigDecimal(rnd.nextInt()));
                        DynamicOrderDeadlock transfer = new DynamicOrderDeadlock();
                        try {
                            transfer.transferMoney2(accounts[fromAcct], accounts[toAcct], amount);
                        } catch (InsufficientResourcesException e) {
                            e.printStackTrace();
                        };
                    }
                    super.run();
                }
            }
            
            for (int i = 0; i < NUM_THREADS; i++)
                new TransferThread().start();
        }
    }
    

    10.1.3オブジェクト間のコラボレーションデッドロック
    プログラムリスト10-5相互協力オブジェクト間のロック順序がデッドロックする(そうしない)
    TaxiクラスのsetLocation()のロック順序とDispatcher.getImage()のロック順序が逆である2つのスレッドがそれぞれこの2つのメソッドを呼び出すとデッドロックが発生する可能性がある
    import java.util.HashSet;
    import java.util.Set;
    
    import net.jcip.annotations.GuardedBy;
    //    10-5                (     )
    public class Taxi {
        @GuardedBy("this")
        private Point location,destination;
        private final Dispatcher dispatcher;
    
        public Taxi(Dispatcher dispatcher) {
            this.dispatcher = dispatcher;
        }
        
        public synchronized Point getLocation() {
            return location;
        }
        //     GPS               
        public synchronized void setLocation(Point location) { //   taxi    :this
            this.location = location;
            if(location.equals(destination)) //         
                //     synchronized ,         
                dispatcher.notifyAvaliable(this);//  dispatcher  
        }
        
    }
    
    class Point{}
    
    class Dispatcher {
        @GuardedBy("this")private final Set taxis;
        @GuardedBy("this")private final Set avaliableTaxis;
        
        public Dispatcher() {
            this.taxis = new HashSet<>();
            this.avaliableTaxis = new HashSet<>();
        }
        
        public synchronized void notifyAvaliable(Taxi taxi) {
            avaliableTaxis.add(taxi);
        }
        public synchronized Image getImage() { //   Dispatcher   :this
            Image image = new Image();
            for(Taxi taxi : taxis)
                image.drawMarker(taxi.getLocation());//   taxi  
            return image;
        }
        
    }
    class Image {
        public void drawMarker(Point point) {
            //TODO .... 
        }
    }
    

    10.1.4オープンコール
    メソッドを呼び出すときにロックを持つ必要がない場合、この呼び出しはオープンコール(OpenCall)と呼ばれます.メソッド宣言にないことを指します.➕ロックですが、より微細なパーティクルのコードブロックを使用して、デッドロックを回避する方法をオープンコールすることができます.パッケージメカニズムを使用してスレッドのセキュリティを提供する方法と同様です.
    プログラムリスト10−6は、互いに連携するオブジェクト間でデッドロックが発生することを回避するために公開的に呼び出される
    import java.util.HashSet;
    import java.util.Set;
    
    import net.jcip.annotations.GuardedBy;
    
    public class Taxi2 {
        @GuardedBy("this")
        private Point location,destination;
        private final Dispatcher2 dispatcher;
    
        public Taxi2(Dispatcher2 dispatcher) {
            this.dispatcher = dispatcher;
        }
        
        public synchronized Point getLocation() {
            return location;
        }
        //     GPS               
        public void setLocation(Point location) { //   taxi    :this
            boolean reachedDestination = false;
            synchronized (this) {
                this.location = location;
                if(location.equals(destination))//         
                    reachedDestination = true;
            }
            //notifyAvaliable  Dispatcher2    ,          Taxi2     
            if(reachedDestination)
                dispatcher.notifyAvaliable(this);
        }
    }
    
    class Dispatcher2 {
        @GuardedBy("this")private final Set taxis;
        @GuardedBy("this")private final Set avaliableTaxis;
        //....
        public Dispatcher2() {
            this.taxis = new HashSet<>();
            this.avaliableTaxis = new HashSet<>();
        }
        
        public synchronized void notifyAvaliable(Taxi2 taxi) {
            avaliableTaxis.add(taxi);
        }
        public Image getImage() {
            Set copy;
            synchronized (this) {
                copy = new HashSet<>(taxis);
            }
            Image image = new Image();
            for(Taxi2 taxi : copy)
                //getLocation  taxi  ,        Dispatcher2     
                image.drawMarker(taxi.getLocation());
            return image;
        }
    }
    

    10.1.5リソースのデッドロック
    複数のスレッドが同じリソースセットで待機している場合、デッドロックegも発生します.
  • タスクは2つのデータベースに接続され、2つのリソースが要求された場合、同じ順序に従うことは保証されません.スレッドAがD 1の接続を持つ、D 2を待つ接続スレッドBは、D 2の接続を持つ、D 1の接続を待つ
  • である.
  • スレッドハングデッドロック(Thread Starvation Deadlock)8.1.1章
  • 10.2デッドロックの回避と診断
  • は、毎回最大で取得することしかできず、デッドロック(通常は現実的ではない)
  • は発生しない.
  • を取得する必要がある場合は、ロックの順序を考慮し、できるだけ減らす必要があります.➕ロックインタラクション数、書き込みドキュメント
  • :2段階戦略(Tow-Part Strategy)  原則:できるだけオープンコール 1を使用します.まず、複数のロック 2を取得する場所(Where)を指定します.次に、グローバル分析を行い、順序が一致することを確認します.

  • 10.2.1タイミングのロックをサポートする
    デッドロックが検出され、デッドロックから復元されます.内部ロックメカニズムの代わりにロック.tryLock()が使用されていることを示します(13章参照).
    区别: 1)内蔵ロック:ロックを取得せず、待ち続ける. 2)表示ロック:ロックが取得されず、タイムアウト時間を指定します.タイムアウト後、Lock.tryLock()は失敗メッセージを返します.
    10.2.2スレッドダンプ(Thread Dump)情報によるデッドロック分析(JVMサポート)
  • Window:Ctrl + Break
  • Unix:Ctrl+またはkill-3、/proc//fd/1
  • に出力
  • jstack:jstack>>出力ファイル
  • 10.3その他の活動性危険
    10.3.1飢餓
    スレッドが必要なリソースにアクセスできずに実行を続行できない場合、飢餓(Starvation)が原因となります.
    優先度は、ロックが適切に保持されていない場合に実行できない構造(リソースを無限にループし、制限なく待機)を処理します.
    10.3.2悪い応答性
    GUIプログラムはバックグラウンドスレッド(実行時間が長い)を使用しており、フロントイベントと競合するCPU不良のロック管理:eg:あるスレッド長時間占有ロック
    10.3.3ライブロック
    通常、ロックは過剰なエラーリカバリコードによって発生します.修復不可能なエラーを修復可能とみなす.スレッドはブロックされませんが、同じ操作が継続的に実行され、常に失敗するため、実行は続行されません.eg:物事を調整する——>ロールバック——>再調整——>再ロールバック——>再調整——>再ロールバック——>......
    2人のあまりにも礼儀正しい人は、道で面と向かって出会って、お互いに相手の道を譲ったが、また別の道で出会った.......だから彼らはこのように繰り返し避けた.
    解決策:再試行メカニズムにランダム性を導入する.eg:後で処理し、後でランダムにします.