zoukankan      html  css  js  c++  java
  • 死锁

      死锁发生在线程之间时,指两个线程各自持有的锁刚好是对方线程请求的,相互等待,系统阻塞。这种死锁往往是线程持有两个以上的锁,且以不同的顺序请求锁导致的,因此也叫锁顺序死锁。看例子:

    package com.wulinfeng.concurrent;
    
    import java.util.Random;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class DemonstrateDeadLock {
        private static final int NUM_THREADS = 20; // 起20个线程处理
        private static final int NUM_ACCOUNTS = 5; // 5个账户进行转账
        private static final int NUM_ITERATIONS = 100000; // 转个10万次
    
        public static void main(String[] args) {
            final DemonstrateDeadLock lock = new DemonstrateDeadLock();
            final Random r = new Random();
            final Account[] accounts = new Account[NUM_ACCOUNTS];
    
            for (int i = 0; i < accounts.length; i++) {
                int money = r.nextInt(100000);
    
                // 为了避免出现转出账户金额小于转账金额的情况,5个账户的初始资金必须1万到10万之间
                if (money < 10000) {
                    money = 10000;
                }
                accounts[i] = lock.new Account(money);
            }
    
            class TransferThread extends Thread {
                public void run() {
                    // 5个账户之间随机相互转账,每次转几块钱,每个线程转个10万次
                    for (int i = 0; i < NUM_ITERATIONS; i++) {
                        int fromAcct = r.nextInt(NUM_ACCOUNTS);
                        int toAcct = r.nextInt(NUM_ACCOUNTS);
    
                        // 避免转给自己
                        if (fromAcct == toAcct) {
                            toAcct = NUM_ACCOUNTS - fromAcct - 1;
                        }
                        DollarAmount amount = lock.new DollarAmount(r.nextInt(10));
                        System.out.printf("第%d次:从账户%d到账户%d转%d元
    ", i, fromAcct, toAcct, amount.getAmount());
                        transferMoneyWithDeadLock(accounts[fromAcct], accounts[toAcct], amount);
                    }
                }
    
                /**
                 * 每次转账都会先锁转出账户A,再锁转入账户B,然后开始转账。因为被调用时参数顺序不可预测,因此锁顺序也不可预测
                 * 如:并发,H线程是A账户->B账户,J线程是B账户->A账户,传参顺序和锁顺序也分别是A->B,B->A,
                 * H线程持有A锁等待B锁,J线程持有B锁等待A锁,互相等待,死锁
                 * 
                 * @param fromAccount
                 * @param toAccount
                 * @param amount
                 */
                public void transferMoneyWithDeadLock(Account fromAccount, Account toAccount, DollarAmount amount) {
                    synchronized (fromAccount) {
                        synchronized (toAccount) {
                            if (fromAccount.getBalance().compareTo(amount) < 0) {
                                throw new IllegalAccessError("not enough money to transfer.");
                            } else {
                                fromAccount.debit(amount);
                                toAccount.credit(amount);
                            }
                        }
                    }
                }
    
                private final Object tieLock = new Object();
    
                /**
                 * 5个账户的hashCode值是唯一的,每次先锁住较小值的账户可以实现每次都按顺序锁
                 * 如H线程是A账户->B账户,J线程是B账户->A账户,A的hashCode:1,B的hashCode:2
                 * 因为hashCode上A<B,所以锁获取顺序H:A->B;J:A->B,两个线程以相同顺序获取锁,不会导致相互等待的情况
                 * 
                 * @param fromAcct
                 * @param toAcct
                 * @param amount
                 */
                public void transferMoneyWithoutDeadLock(final Account fromAcct, final Account toAcct,
                        final DollarAmount amount) {
                    class Helper {
                        public void transfer() {
                            if (fromAcct.getBalance().compareTo(amount) < 0) {
                                throw new IllegalAccessError("not enough money to transfer.");
                            } else {
                                fromAcct.debit(amount);
                                toAcct.credit(amount);
                            }
                        }
                    }
                    int fromHash = System.identityHashCode(fromAcct);
                    int toHash = System.identityHashCode(toAcct);
    
                    if (fromHash < toHash) {
                        synchronized (fromAcct) {
                            synchronized (toAcct) {
                                new Helper().transfer();
                            }
                        }
                    } else if (fromHash > toHash) {
                        synchronized (toAcct) {
                            synchronized (fromAcct) {
                                new Helper().transfer();
                            }
                        }
                    } else {
                        synchronized (tieLock) {
                            synchronized (fromAcct) {
                                synchronized (toAcct) {
                                    new Helper().transfer();
                                }
                            }
                        }
                    }
                }
    
                public void transferMoneyWithLock(Account fromAcct, Account toAcct, DollarAmount amount) {
                    long timeout = 100; // 转账超过100毫秒即超时
                    long stopTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout);
                    while (true) {
                        if (fromAcct.lock.tryLock()) { // 转入账户获取到锁才进入下面逻辑处理,获取不到则退出
                            try {
                                if (toAcct.lock.tryLock()) { // 转出账户获取到锁才进入下面逻辑处理,获取不到则退出
                                    try {
                                        if (fromAcct.getBalance().compareTo(amount) < 0) {
                                            throw new IllegalAccessError("not enough money to transfer.");
                                        } else {
                                            fromAcct.debit(amount);
                                            toAcct.credit(amount);
                                            return; // 转账成功,退出循坏
                                        }
                                    } finally {
                                        toAcct.lock.unlock(); // 释放转出账户锁
                                    }
                                }
                            } finally {
                                fromAcct.lock.unlock(); // 释放转出账户锁
                            }
                        }
    
                        // 走到这里,说明获取转入或者转出锁失败,若不超时则退出重入
                        if (System.nanoTime() < stopTime) {
                            return;
                        }
    
                        // 否则休眠随机时间,避免活锁
                        try {
                            Thread.sleep(r.nextLong());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
    
            for (int i = 0; i < NUM_THREADS; i++) {
                new TransferThread().start();
            }
        }
    
        /**
         * 账户,提供转出、转入方法
         * 
         * @author Administrator
         *
         */
        class Account {
            private DollarAmount balance;
    
            private Lock lock;
    
            public Account(int account) {
                this.balance = new DollarAmount(account);
                this.lock = new ReentrantLock();
            }
    
            public DollarAmount getBalance() {
                return balance;
            }
    
            public void debit(DollarAmount amount) {
                this.balance.remove(amount);
            }
    
            public void credit(DollarAmount amount) {
                this.balance.add(amount);
            }
        }
    
        /**
         * 转账金额,提供加钱、减钱方法
         * 
         * @author Administrator
         *
         */
        class DollarAmount {
            private int amount;
    
            public DollarAmount(int amount) {
                this.amount = amount;
            }
    
            public int compareTo(DollarAmount amount2) {
                int result;
                if (this.amount > amount2.getAmount()) {
                    result = 1;
                } else if (this.amount < amount2.getAmount()) {
                    result = -1;
                } else {
                    result = 0;
                }
                return result;
            }
    
            public int add(DollarAmount amount) {
                return this.amount += amount.getAmount();
            }
    
            public int remove(DollarAmount amount) {
                return this.amount -= amount.getAmount();
            }
    
            public int getAmount() {
                return amount;
            }
        }
    }

      解决死锁有两种方法,一是使用内置锁通过保证锁顺序一致,参见上面代码里的transferMoneyWithoutDeadLock方法;二是使用显式锁的tryLock进行重试(获取锁失败则退出而不是阻塞),参见上面的transferMoneyWithLock方法。

      另一种比较隐蔽的锁顺序不一致的情况发生在对象的方法调用,见下面代码:

    package com.wulinfeng.concurrent;
    
    import java.util.HashSet;
    import java.util.Set;
    
    public class ObjectsDeadLock {
        private static final int NUM_THREADS = 20; // 起20个线程处理
        // 随机产生的字符串
        private static final String[] msgs = new String[] { "abc", "dfg", "hij", "lmn", "opq", "rst", "wlf", "xyz" };
        private static final int NUM_ITERATIONS = 100000; // 每个线程跑个10万次
    
        public static void main(String[] args) {
            final ObjectsDeadLock lock = new ObjectsDeadLock();
    
            final Dispatcher dispatcher = lock.new Dispatcher();
            final Taxi taxi = lock.new Taxi(dispatcher);
    
            class WorkThread extends Thread {
                public void run() {
                    for (int i = 0; i < NUM_ITERATIONS; i++) {
                        taxi.setStrDead(msgs[i % msgs.length]);
                        dispatcher.getStrDead(i);
                    }
                };
            }
    
            for (int i = 0; i < NUM_THREADS; i++) {
                new WorkThread().start();
            }
        }
    
        class Taxi {
            private String msg;
            private final Dispatcher dispatcher;
    
            public Taxi(Dispatcher dispatcher) {
                this.dispatcher = dispatcher;
            }
    
            public synchronized String getStr() {
                return msg;
            }
    
            /**
             * 该方法先给自己加锁,再调用Dispatcher对象的加锁方法notifyAvailable,加锁顺序Taxi->Dispatcher
             * 
             * @param msg
             */
            public synchronized void setStrDead(String msg) {
                if (msg.equals("wlf")) {
                    this.msg = msg;
                    dispatcher.notifyAvailable(this);
                }
            }
    
            /**
             * 只给自己加锁,调用其他对象方法不加锁:锁Taxi->释放Taxi->锁Dispatcher->释放Dispatcher
             * 
             * @param msg
             */
            public void setStrNotDead(String msg) {
                boolean available = false;
                synchronized (this) {
                    if (msg.equals("wlf")) {
                        this.msg = msg;
                        available = true;
                    }
                }
                if (available) {
                    dispatcher.notifyAvailable(this);
                }
    
            }
        }
    
        class Dispatcher {
            private final Set<Taxi> availableTaxis;
    
            public Dispatcher() {
                availableTaxis = new HashSet<Taxi>();
            }
    
            public synchronized void notifyAvailable(Taxi taxi) {
                availableTaxis.add(taxi);
            }
    
            /**
             * 该方法里调用Taxi对象的加锁方法getStr,加锁顺序Dispatcher->Taxi
             */
            public synchronized void getStrDead(int count) {
                for (Taxi t : availableTaxis) {
                    System.out.println(t.getStr() + ": " + count);
                }
                availableTaxis.clear(); // 清空集合,准备迎接下一个符合条件的对象
            }
    
            /**
             * 只给自己加锁,调用其他对象方法不加锁:锁Dispatcher->释放Dispatcher->锁Taxi->释放Taxi
             * 
             * @param count
             */
            public void getStrNotDead(int count) {
                Set<Taxi> copy;
    
                // 复制给当前线程读,不影响其他线程写,读写分离
                synchronized (this) {
                    copy = new HashSet<Taxi>(availableTaxis);
                }
                for (Taxi t : copy) {
                    System.out.println(t.getStr() + ": " + count);
                }
    
                // 读完清空
                synchronized (this) {
                    availableTaxis.clear();
                }
            }
        }
    }

      这里无法改变线程里锁的顺序,因此只能减锁,就是在加锁方法A里调用另一个对象的加锁方法B时,缩小加锁的代码块,让加锁方法B逃离方法A的锁。run方法里改两行代码再跑一次,死锁不再出现:

                public void run() {
                    for (int i = 0; i < NUM_ITERATIONS; i++) {
                        taxi.setStrNotDead(msgs[i % msgs.length]);
                        dispatcher.getStrNotDead(i);
                    }
                };

      死锁发生在线程池中时,指的是池里的某任务无限期的等待它所依赖的其他任务,这也叫线程饥饿死锁。看例子:

    package com.wulinfeng.concurrent;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class ThreadDeadlock {
        ExecutorService es = Executors.newSingleThreadExecutor();
    
        public class ReaderTask implements Callable<String> {
            Future<String> header;
            String body = "Hell, world!";
            String result = null;
    
            @Override
            public String call() throws Exception {
                // 单线程池提交子任务
                header = es.submit(new LoadFileTask("welcome!"));
                result = header.get() + "
    " + body + "
    ";
                System.out.printf("result:
    %s", result);
                return result;
            }
        }
    
        public class LoadFileTask implements Callable<String> {
            private String msg;
    
            public LoadFileTask(String msg) {
                this.msg = msg;
            }
    
            public String call() {
                try {
                    Thread.sleep(1000); // 模拟文件下载,耗时1秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return msg;
            }
        }
    
        public static void main(String[] args) {
            ThreadDeadlock t = new ThreadDeadlock();
            try {
                // 单线程池提交父任务
                Future<String> father = t.es.submit(t.new ReaderTask());
                // 获取父任务结果依赖子任务结果,线程池只允许一个线程执行,父等子执行,子等父让位
                System.out.printf("out:
    %s", father.get());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                t.es.shutdown();
            }
        }
    }

      上面使用了单线程的线程池,也就是每次只能容纳一个线程做串行执行的线程池。父线程需要启动子线程获取结果才能返回,而子线程需要父线程执行结束才能启动,因此父线程和子线程互相无限等待。解决方式很简单,线程池需要允许容纳两个以上的线程就可避免线程饥饿,如把newSingleThreadExecutor改为newCachedThreadPool。  

  • 相关阅读:
    Java实现 蓝桥杯VIP 算法提高 交换Easy
    Java实现 蓝桥杯VIP 算法提高 多项式输出
    Java实现 蓝桥杯VIP 算法提高 多项式输出
    Java实现 蓝桥杯VIP 算法提高 多项式输出
    Java实现 蓝桥杯VIP 算法提高 多项式输出
    Java实现 蓝桥杯VIP 算法提高 多项式输出
    Java实现 蓝桥杯VIP 算法训练 矩阵乘方
    QT中给各控件增加背景图片(可缩放可旋转)的几种方法
    回调函数实现类似QT中信号机制
    std::string的Copy-on-Write:不如想象中美好(VC不使用这种方式,而使用对小字符串更友好的SSO实现)
  • 原文地址:https://www.cnblogs.com/wuxun1997/p/6892853.html
Copyright © 2011-2022 走看看