Concurrent Deadlock

1 What is a deadlock


2 deadlock impact

Influence deadlock in different systems is not the same, depending on the system's ability to handle deadlock

  • Database : Detects and abandon the transaction
  • JVM : Unable to automatically process

3 probability is not high but the big harm

  • Not necessarily happen, but to comply with " Murphy's Law "
  • In the event, mostly high concurrency scenarios, multi-user impact
  • The whole system crashes , crashes subsystem, performance degradation
  • Stress tests can not identify all potential deadlocks

4 is an example of deadlock

4.1 In the simplest case

/**
 * 必定发生死锁的情况
 */
public class MustDeadLock implements Runnable {
    static final Object LOCK_A = new Object();
    static final Object LOCK_B = new Object();
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"进入了run方法...");
        if ("A".equals(Thread.currentThread().getName())) {
            synchronized (LOCK_A) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK_B) {
                    System.out.println("线程A成功拿到两把锁");
                }
            }
        }
        if ("B".equals(Thread.currentThread().getName())) {
            synchronized (LOCK_B) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK_A) {
                    System.out.println("线程B成功拿到两把锁");
                }
            }
        }
    }
    public static void main(String[] args) {
        MustDeadLock run = new MustDeadLock();
        Thread t1 = new Thread(run, "A");
        Thread t2 = new Thread(run, "B");
        t1.start();
        t2.start();
    }
}

Examples of actual production of 4.2: Transfer

/**
 * 转账时候遇到死锁,一旦打开注释,便会发生死锁
 */
public class TransferMoney {
    //A的账户余额
    static Account a = new Account(500);
    //B的账户余额
    static Account b = new Account(500);
    /**
     * 转账业务
     * @param from   钱从哪里来
     * @param to     钱到哪里去
     * @param amount 转账金额
     */
    public static void transferMoney(Account from, Account to, int amount) {
        //注意这里的加锁对象是谁
        synchronized (from) {
            //try {
            //    Thread.sleep(500);
            //} catch (InterruptedException e) {
            //    e.printStackTrace();
            //}
            synchronized (to) {
                if (from.balance - amount < 0) {
                    System.out.println(Thread.currentThread().getName() + "余额不足,转账失败。");
                }
                from.balance -= amount;
                to.balance += amount;
                System.out.println(Thread.currentThread().getName() + "成功转账" + amount + "元");
            }
        }
    }
    //账号类
    static class Account {
        //余额
        int balance;
        public Account(int balance) {
            this.balance = balance;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Runnable run = () -> {
            //A->B
            if ("A".equals(Thread.currentThread().getName())) {
                transferMoney(a, b, 200);
            }
            //B->A
            if ("B".equals(Thread.currentThread().getName())) {
                transferMoney(b, a, 300);
            }
        };
        Thread t1 = new Thread(run, "A");
        Thread t2 = new Thread(run, "B");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("a的余额" + a.balance);
        System.out.println("b的余额" + b.balance);
    }
}

4.3 analog multiplayer random transfer

50,000 people a lot, but still a deadlock occurs, Murphy's Law

Review: deadlock probability is not high , but great harm

public class TransferMoney {
    /**
     * 转账业务
     * @param from   钱从哪里来
     * @param to     钱到哪里去
     * @param amount 转账金额
     */
    public static void transferMoney(Account from, Account to, int amount) {
        //注意这里的加锁对象是谁
        synchronized (from) {
            synchronized (to) {
                if (from.balance - amount < 0) {
                    System.out.println(Thread.currentThread().getName() + "余额不足,转账失败。");
                }
                from.balance -= amount;
                to.balance += amount;
                System.out.println(Thread.currentThread().getName() + "成功转账" + amount + "元");
            }
        }
    }
    //账号类
    static class Account {
        //余额
        int balance;
        public Account(int balance) {
            this.balance = balance;
        }
    }
}
/**
 * 多人同时转账,依然很危险
 */
public class MultiTransferMoney {
    public static void main(String[] args) {
        //账户数量(数量越大发生死锁机率越小)
        int numAccounts = 5000;
        //每个账户的初始金额
        int numMoney = 1000;
        //转账次数(次数越小发生死锁机率越小)
        int numIterations = 1000000;
        //多少个人模拟转账
        int numThreads = 20;
        Random rnd = new Random();
        Account[] accounts = new Account[numAccounts];
        //初始化账户
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = new Account(numMoney);
        }
        Runnable run = () -> {
            //我转给你,你转给我的概率很小
            for (int i = 0; i < numIterations; i++) {
                int fromAcct = rnd.nextInt(numAccounts);
                int toAcct = rnd.nextInt(numAccounts);
                int amount = rnd.nextInt(numMoney);
                TransferMoney.transferMoney(accounts[fromAcct], accounts[toAcct], amount);
            }
            System.out.println("运行结束");
        };
        //模拟numThreads个人同时转账
        for (int i = 0; i < numThreads; i++) {
            new Thread(run).start();
        }
        //形成环了,卡死
    }
}

4 5 deadlock prerequisite

Indispensable, while meeting

  1. Mutually exclusive conditions
  2. Request to maintain the conditions
  3. Not deprivation
  4. Circular wait condition

6 how to locate a deadlock?

6.1 jstack

Examples of practical production execution 4.2: Transfer code

1. Locate the java program corresponding to the process pid   jvisualvm

2.  execution jstack command jstack 40732

 

4.3 Analysis of simulation multiplayer random transfer

6.2 ThreadMXBean

/**
 * 用ThreadMXBean检测死锁
 */
public class ThreadMXBeanDetection implements Runnable {
    static final Object LOCK_A = new Object();
    static final Object LOCK_B = new Object();
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "进入了run方法...");
        if ("A".equals(Thread.currentThread().getName())) {
            synchronized (LOCK_A) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK_B) {
                    System.out.println("线程A成功拿到两把锁");
                }
            }
        }
        if ("B".equals(Thread.currentThread().getName())) {
            synchronized (LOCK_B) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK_A) {
                    System.out.println("线程B成功拿到两把锁");
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ThreadMXBeanDetection run = new ThreadMXBeanDetection();
        Thread t1 = new Thread(run, "A");
        //System.out.println("线程A,id=" + t1.getId());
        Thread t2 = new Thread(run, "B");
        //System.out.println("线程B,id=" + t2.getId());
        t1.start();
        t2.start();
        //让它进入死锁
        Thread.sleep(1000);
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        //发现死锁的线程
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        if (deadlockedThreads != null && deadlockedThreads.length > 0) {
            for (int i = 0; i < deadlockedThreads.length; i++) {
                ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
                System.out.println("发现死锁:" + threadInfo.getThreadName() + " id:" + deadlockedThreads[i]);
            }
        }
    }
}

 

7 修复死锁的策略

线上问题都需要防患于未然,不造成损失地扑灭几乎已经是不可能

保存案发现场然后立刻重启服务器

暂时保证线上服务的安全,然后在利用刚才保存的信息,排查死锁,修改代码,重新发版

7.1 避免策略 

避免策略:哲学家就餐的换手方案、转账换序方案

思路:避免相反的获取锁的顺序

  • 转账时避免死锁
  • 实际上不在乎获取锁的顺序
  • 通过hashcode来决定获取锁的顺序、冲突时需要 “加时赛
  • 主键就更方便

public class TransferMoney {
    //A的账户余额
    static Account a = new Account(500);
    //B的账户余额
    static Account b = new Account(500);
    static final Object LOCK = new Object();
    /**
     * 转账业务
     * @param from   钱从哪里来
     * @param to     钱到哪里去
     * @param amount 转账金额
     */
    public static void transferMoney(Account from, Account to, int amount) {
        class Helper {
            public void transfer() {
                if (from.balance - amount < 0) {
                    System.out.println(Thread.currentThread().getName() + "余额不足,转账失败。");
                    return;
                }
                from.balance -= amount;
                to.balance += amount;
                System.out.println(Thread.currentThread().getName() + "成功转账" + amount + "元");
            }
        }
        //获取锁的顺序一致
        //AB转账,A的hash值小于B
        //A->B 先获取A的锁,在获取B的锁
        //B->A 先获取A的锁,在获取B的锁
        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);
        if (fromHash < toHash) {
            synchronized (from) {
                synchronized (to) {
                    new Helper().transfer();
                }
            }
        } else if (fromHash > toHash) {
            synchronized (to) {
                synchronized (from) {
                    new Helper().transfer();
                }
            }
        }
        //通过hashcode来决定获取锁的顺序、冲突时需要 “加时赛”
        else {
            synchronized (LOCK) {
                synchronized (to) {
                    synchronized (from) {
                        new Helper().transfer();
                    }
                }
            }
        }
    }
    //账号类
    static class Account {
        //余额
        int balance;

        public Account(int balance) {
            this.balance = balance;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            //A->B
            transferMoney(a, b, 200);
        }, "A");
        Thread t2 = new Thread(() -> {
            //B->A
            transferMoney(b, a, 300);
        }, "B");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("a的余额" + a.balance);
        System.out.println("b的余额" + b.balance);
    }
}

代码演示:哲学家进入死锁

多种解决方案

死锁资源耗尽的风险

  1. 服务员检查(避免策略)
  2. 改变—个哲学家拿叉子的顺序(避免策略)
  3. 餐票(避免策略)
  4. 领导调节(检测与恢复策略)
/**
 * 演示哲学家就餐问题导致的死锁
 */
public class DiningPhilosophers {
    //哲学家
    public static class Philosopher implements Runnable {
        //左边筷子
        private Object leftChopstick;
        //右边筷子
        private Object rightChopstick;
        public Philosopher(Object leftChopstick, Object rightChopstick) {
            this.leftChopstick = leftChopstick;
            this.rightChopstick = rightChopstick;
        }
        @Override
        public void run() {
            try {
                while (true) {
                    //思考
                    doAction("Thinking");
                    synchronized (leftChopstick) {
                        //拿起左边的筷子
                        doAction("Picked up left chopstick");
                        synchronized (rightChopstick) {
                            //拿起右边的筷子
                            doAction("Picked up right chopstick - eating");
                            //放下右边的筷子
                            doAction("Put down right chopstick");
                        }
                        //放下左边的筷子
                        doAction("Put down left chopstick");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //动作:打印+等待
        private void doAction(String action) throws InterruptedException {
            System.out.println(Thread.currentThread().getName() + " " + action);
            //[0,10)
            Thread.sleep((long) (Math.random() * 10));
        }
    }
    public static void main(String[] args) {
        //哲学家数组
        Philosopher[] philosophers = new Philosopher[5];
        //筷子数组 筷子跟数==哲学家人数
        Object[] chopsticks = new Object[philosophers.length];
        //初始化
        for (int i = 0; i < chopsticks.length; i++) {
            chopsticks[i] = new Object();
        }
        for (int i = 0; i < philosophers.length; i++) {
            //左筷子
            Object leftChopstick = chopsticks[i];
            //右筷子
            Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];
            //1.发生死锁 都是Picked up left chopstick
            //philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
            
            //2.改变—个哲学家拿叉子的顺序(避免策略)
            //让最后一个哲学家,反着拿
            if (i == philosophers.length - 1) {
                philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
            } else {
                philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
            }
            new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();
        }
    }
}

7.2 检测与恢复策略 

检测与恢复策略:一段时间检测是否有死锁,如果有就剥夺某一个资源,来打开死锁

7.3 鸵鸟策略

鸵鸟策略:鸵鸟这种动物在遇到危险的时候,通常就会把头埋在地上,这样一来它就看不到危险了。而鸵鸟策略的意思就是说,如果我们发生死锁的概率极其低,那么我们就直接忽略它,直到死锁发生的时候,再人工修复


8 实际工程中如何避免死锁

 

代码演示:退一步海阔天空

/**
 * 用tryLock来避免死锁
 */
public class TryLockDeadlock implements Runnable {
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();
    @Override
    public void run() {
        //尝试获取次数
        for (int i = 0; i < 100; i++) {
            if ("A".equals(Thread.currentThread().getName())) {
                try {
                    if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                        System.out.println("线程A获取到了锁1");
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
                            System.out.println("线程A获取到了锁2");
                            System.out.println("线程A成功获取到了两把锁");
                            //do something
                            lock2.unlock();
                            lock1.unlock();
                            break;
                        } else {
                            System.out.println("线程A尝试获取锁2失败,已重试");
                            lock1.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    } else {
                        System.out.println("线程A获取锁1失败,已重试");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if ("B".equals(Thread.currentThread().getName())) {
                try {
                    if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)) {
                        System.out.println("线程B获取到了锁2");
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock1.tryLock(3000, TimeUnit.MILLISECONDS)) {
                            System.out.println("线程B获取到了锁1");
                            System.out.println("线程B成功获取到了两把锁");
                            //do something
                            lock1.unlock();
                            lock2.unlock();
                            break;
                        } else {
                            System.out.println("线程B尝试获取锁1失败,已重试");
                            lock2.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    } else {
                        System.out.println("线程B获取锁2失败,已重试");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) {
        TryLockDeadlock run = new TryLockDeadlock();
        new Thread(run,"A").start();
        new Thread(run,"B").start();
    }
}

9 其他活性故障

  • 死锁是最常见的活跃性问题,不过除了死锁之外,还有一些类似的问题,会导致程序无法顺利执行,统称为活跃性问题
  • 活锁(LiveLock)
  • 饥饿

9.1 活锁

9.1.2 什么是活锁

9.1.3 代码演示

/**
 * 演示活锁问题
 */
public class LiveLock {
    //餐具:勺子(只有一个)
    static class Spoon {
        //勺子拥有者
        private Diner owner;
        public Spoon(Diner owner) {
            this.owner = owner;
        }
        public void setOwner(Diner owner) {
            this.owner = owner;
        }
        //使用勺子
        public synchronized void use() {
            System.out.printf("%s吃完了!", owner.name);
        }
    }
    //就餐者:正在吃饭的那个人
    static class Diner {
        //就餐者名字
        private String name;
        //就餐者是否饥饿
        private boolean isHungry;
        public Diner(String name) {
            this.name = name;
            //初始化饥饿
            isHungry = true;
        }
        /**
         * 就餐方法
         * @param spoon 餐具(物资匮乏,只有一个)
         * @param spouse 陪我一起就餐的人
         */
        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) {
                //发现勺子拥有者不是自己,伴侣可能正在用
                if (spoon.owner != this) {
                    try {
                        //等伴侣吃完
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                //已经拿到勺子了
                //开始谦让,先询问对方是否饥饿

                //!!!发生活锁,互相谦让,把勺子给对方
                //if (spouse.isHungry) {

                //原因:重试机制不变,消息队列始终重试,吃饭始终谦让
                //解决:加入随机因素(以太网的指数退避算法)
                if (spouse.isHungry && new Random().nextInt(10) < 9) {
                    System.out.println(name + ": 亲爱的" + spouse.name + "你先吃吧");
                    //勺子给对方
                    spoon.setOwner(spouse);
                    continue;
                }
                //对方不饿,自己吃
                spoon.use();
                //不在饥饿
                isHungry = false;
                System.out.println(name + ": 我吃完了");
                //吃完了,勺子给对方
                spoon.setOwner(spouse);
            }
        }
    }
    public static void main(String[] args) {
        Diner husband = new Diner("药水哥");
        Diner wife = new Diner("周淑怡");
        //物资匮乏,只有一个勺子
        Spoon spoon = new Spoon(husband);
        new Thread(() -> husband.eatWith(spoon, wife)).start();
        new Thread(() -> wife.eatWith(spoon, husband)).start();
    }
}

9.1.4 工程中的活锁实例:消息队列

9.1.5 如何解决活锁问题

加入随机因素


9.2 饥饿

当线程需要某些资源(例如CPU),但是却始终得不到

线程的优先级设置得过于低,或者有某线程持有锁同时又无限循环从而不释放锁,或者某程序始终占用某文件的写锁

饥饿可能会导致响应性差:比如,我们的浏览器有一个线程负责处理前台响应(打开收藏夹等动作),另外的后台线程负责下载图片和文件、计算渲染等。在这种情况下,如果后台线程把CPU资源都占用了,那么前台线程将无法得到很好地执行,这会导致用户的体验很差


10 常见面试问题

1. 写一个必然死锁的例子,生产中什么场景下会发生死锁?

  • 在一个方法中获取多个锁,容易产生死锁

2. 发生死锁必须满足哪些条件

  1. 互斥条件
  2. 请求与保持条件
  3. 不剥夺条件
  4. 循环等待条件

3. 如何定位死锁?

  1. jstack
  2. ThreadMXBean

4. 有哪些解决死锁问题的策略

  1. 避免策略:哲学家就餐的换手方案、转账换序方案
  2. 检测与恢复策略:一段时间检测是否有死锁,如果有就剥夺某一个资源,来打开死锁
  3. 鸵鸟策略:鸵鸟这种动物在遇到危险的时候,通常就会把头埋在地上,这样一来它就看不到危险了。而鸵鸟策略的意思就是说,如果我们发生死锁的概率极其低,那么我们就直接忽略它,直到死锁发生的时候,再人工修复

5.讲一讲经典的哲学家就餐问题

  1. 画出示意图
  2. 给出多种解决方案

  1. 服务员检查(避免策略)
  2. 改变一个哲学家拿叉子的顺序(避免策略)
  3. 餐票(避免策略)
  4. 领导调节(检测与恢复策略)

6. 实际工程中如何避免死锁?

  1. 设置超时时间
  2. 多使用并发类而不是自己设计锁
  3. 尽量降低锁的使用粒度:用不同的锁而不是一个锁
  4. 如果能使用同步代码块,就不使用同步方法:自己指定锁对象
  5. 给你的线程起个有意义的名字:debug和排查时事半功倍,框架和JDK都遵守这个最佳实践
  6. 避免锁的嵌套:MustDeadLock类
  7. 分配资源前先看能不能收回来:银行家算法
  8. 尽量不要几个功能用同一把锁:专锁专用

7. 什么是活跃性问题活锁、饥饿和死锁有什么区别?

发布了515 篇原创文章 · 获赞 97 · 访问量 108万+

Guess you like

Origin blog.csdn.net/qq_40794973/article/details/103978157