1 什么是死锁?

1、发生在并发中(单线程不会死锁)
2、互不相让(>=2个线程互相持有对方所需要的资源)

两个线程造成死锁的情况:

多个线程造成死锁的情况:
多个线程的依赖关系是环形的

2 死锁的影响

死锁的影响在不同的系统中不一样
取决于系统对死锁的处理能力

数据库:检测并放弃事务

JVM:无法自动处理(死锁的处理有很多种,JVM不能做到十全十美)【有工具可以检测】

几率不高但危害大
1)一旦发生,多是高并发场景,影响了多个用户
2)整个系统崩溃、子系统崩溃、性能降低
注意:压力测试无法找出所有潜在的死锁(死锁与并发量有关系,但是不是确定性的关系,而死锁是随机触发的)

3发生死锁的例子

3.1最简单的情况

/**
 * @description 必定发生死锁的情况
 */
public class MustDeadLock implements Runnable {
    int flag = 1;
    static Object o1 = new Object();
    static Object o2 = new Object();

    public static void main(String[] args) throws InterruptedException {
        MustDeadLock r1 = new MustDeadLock();
        MustDeadLock r2 = new MustDeadLock();
        r1.flag = 1;
        r2.flag = 0;
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);

        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("flag = " + flag);

        if (flag == 1) {
            synchronized (o1) {
                try {
                    System.out.println(Thread.currentThread().getName() + " 获得o1");
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getName() + " 等待o2");
                    synchronized (o2) {
                        System.out.println(Thread.currentThread().getName() + " 获得o1和o2");
                        Thread.sleep(500);
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    System.out.println(Thread.currentThread().getName() + " 获得o2");
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getName() + " 等待o1");
                    synchronized (o1) {
                        System.out.println(Thread.currentThread().getName() + " 获得o1和o2");
                        Thread.sleep(500);
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

图示:

运行结果:

注意看退出信号:Exit code 130(不正常退出)

3.2实际生产中的例子

转账
1)需要两把锁(一把锁自己,一把锁对方)
2)获得两把锁成功,余额>0,扣除转出人,增加收款人的余额,是原子操作

3)顺序相反导致死锁

3.3模拟多人随机转账

5万人,依然会发生死锁


import java.util.Random;

/**
 * 多人同时转账 依然很危险
 */
public class MultiTransferMoney {
    private static final int NUM_ACCOUNTS = 50;
    private static final int NUM_MONEY = 1000;
    private static final int NUM_ITERATIONS = 1000000;
    private static final int NUM_THREAD = 20;

    public static void main(String[] args) {
        Random random = new Random();
        TransferMoney.Account[] accounts = new TransferMoney.Account[NUM_ACCOUNTS];
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = new TransferMoney.Account(i, NUM_MONEY);
        }
        class TransferThread extends Thread {
            @Override
            public void run() {
                for (int i = 0; i < NUM_ITERATIONS; i++) {
                    int fromAcct = random.nextInt(NUM_ACCOUNTS);
                    int toAcct = random.nextInt(NUM_ACCOUNTS);
                    int amount = random.nextInt(NUM_MONEY);
                    if (fromAcct != toAcct) {
                        TransferMoney.transferMoney(accounts[fromAcct], accounts[toAcct], amount);
                    }
                }
                System.out.println("==========运行结束==========");
            }
        }
        // 创建20个线程
        for (int i = 0; i < NUM_THREAD; i++) {
            new TransferThread().start();
        }
    }
}

4 死锁的4个必要条件

(1)互斥
(2)请求与保持(不释放)
(3)不剥夺(没有裁判能够剥夺某个线程对资源的保持)
(4)循环等待(循环依赖)

1)jstack分析死锁

1、找到java程序的pid(jps)
2、${JAVA_HOME}/bin/jstack -l pid

分析“多人转账”

2) ThreadMXBean代码演示(裁判,定位并修复死锁)

(1)ManagementFactory.getThreadMXBean()
(2)打印所有死锁的线程信息

        
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

/**
 * 用ThreadMXBean检测死锁
 */
public class ThreadMXBeanDetection implements Runnable {

    int flag = 1;
    static Object o1 = new Object();
    static Object o2 = new Object();

    public static void main(String[] args) throws InterruptedException {
        ThreadMXBeanDetection r1 = new ThreadMXBeanDetection();
        ThreadMXBeanDetection r2 = new ThreadMXBeanDetection();
        r1.flag = 1;
        r2.flag = 0;
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);

        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());
            }
        }
    }

    @Override
    public void run() {
        System.out.println("flag = " + flag);

        if (flag == 1) {
            synchronized (o1) {
                try {
                    System.out.println(Thread.currentThread().getName() + " 获得o1");
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getName() + " 等待o2");
                    synchronized (o2) {
                        System.out.println(Thread.currentThread().getName() + " 获得o1和o2");
                        Thread.sleep(500);
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    System.out.println(Thread.currentThread().getName() + " 获得o2");
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getName() + " 等待o1");
                    synchronized (o1) {
                        System.out.println(Thread.currentThread().getName() + " 获得o1和o2");
                        Thread.sleep(500);
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

5.修复死锁的策略
1)线上
1、防患于未然(不可提前预料、蔓延速度快、危害大)
2、保持案发现场,立刻重启服务器
3、保证线上服务的安全,根据保存的案发现场,排查死锁,修改代码
2)线下
1、避免策略(哲学家就餐-换手、转账换序)
思路:避免相反的获取锁的顺序
(锁获取的顺序不影响该策略的执行)

如果hashCode相同,则需要加时赛

如果数据库,则主键用来避免死锁更好

2、检测与恢复策略(有死锁,则剥夺)
3、鸵鸟策略(忽略概率低的死锁,出现死锁再人工修复)