技术自查番外篇六:死锁及解决办法

5 篇文章 0 订阅
5 篇文章 0 订阅

前言

线程的六种状态(初始,等待,阻塞,运行,就绪和等待),我们都知道,但实际情况下,会出现这六种状态下的,第七种情况死锁,那么死锁是什么,为什么会发生呢?

先了解下死锁的概念

概念

两个线程同一时间持有自身的锁且互相争夺对方的锁,等待对方锁释放,如果没有外力介入下,这两个线程会一直等待下去。

虽然说一直等待,可以说是等待状态,但由于它们互相持有对方所需的锁,此时就是阻塞状态了。

发生死锁条件

1. 互斥条件

2. 持有并等待状态(持有本身锁且等待对方锁)

3. 不可剥夺条件(不可释放本身锁)

4. 环路等待状态(死循环)

栗子

/**
 * @author Leo
 * @description
 * @createDate 2021/8/30 22:22
 **/
public class MyLock {
    public static Object obj1 = new Object();
    public static Object obj2 = new Object();
}

/**
 * @author Leo
 * @description
 * @createDate 2021/8/30 22:21
 **/
public class DieLock implements Runnable {

    private boolean flag;

    public DieLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            while (true) {
                synchronized (MyLock.obj1) {
                    System.out.println(Thread.currentThread().getName() + "....if...obj1..." + flag);
                    synchronized (MyLock.obj2) {
                        System.out.println(Thread.currentThread().getName() + ".....if.....obj2....." + flag);

                    }
                }
            }
        } else {
            while (true) {
                synchronized (MyLock.obj2) {
                    System.out.println(Thread.currentThread().getName() + "....else...obj2..." + flag);
                    synchronized (MyLock.obj1) {
                        System.out.println(Thread.currentThread().getName() + ".....else.....obj1....." + flag);

                    }
                }
            }
        }

    }
}

/**
 * @author Leo
 * @description
 * @createDate 2021/9/4 21:16
 **/
public class DieDemo {

    public static void main(String[] args) {
        DieLock d1 = new DieLock(true);
        DieLock d2 = new DieLock(false);
        Thread t1 = new Thread(d1);
        Thread t2 = new Thread(d2);
        t1.start();
        t2.start();
    }

}
运行结果:
Thread-0....if...obj1...true
Thread-1....else...obj2...false

栗子2

/**
 * @author Leo
 * @description lock版死锁
 * @createDate 2021/9/16 23:13
 **/
public class DieLockLock {
    public static void main(String[] args) {
        
        Lock lockA = new ReentrantLock(); // 创建锁 A
        Lock lockB = new ReentrantLock(); // 创建锁 B

        // 创建线程 1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                lockA.lock(); // 加锁
                System.out.println("线程 1:获取到锁 A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 1:等待获取 B...");
                    lockB.lock(); // 加锁
                    try {
                        System.out.println("线程 1:获取到锁 B!");
                    } finally {
                        lockB.unlock(); // 释放锁
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); // 释放锁
                }
            }
        });
        t1.start(); // 运行线程

        // 创建线程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lockB.lock(); // 加锁
                System.out.println("线程 2:获取到锁 B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 2:等待获取 A...");
                    lockA.lock(); // 加锁
                    try {
                        System.out.println("线程 2:获取到锁 A!");
                    } finally {
                        lockA.unlock(); // 释放锁
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockB.unlock(); // 释放锁
                }
            }
        });
        t2.start(); // 运行线程
    }
}
日志
线程 1:获取到锁 A!
线程 2:获取到锁 B!
线程 1:等待获取 B...
线程 2:等待获取 A...

从上面栗子可知

1. 互斥条件

t1,t2线程分别持有d1,d2锁,同时又想获取对方的锁,由于锁的互斥性,导致同一个锁同一时间不可被多个线程持有

2. 持有并等待状态、不可剥夺状态

t1,t2线程分别持有d1,d2锁,同时又想获取对方的锁,由于锁的互斥性,同时加上本身自身逻辑问题,又不能释放本身锁,这就导致最后两个线程互相持有本身锁,处于等待状态和不可剥夺状态

3.  环路等待状态

刚才也说过,t1等待d2,t2等待d1,形成死循环

 如何排查线程问题

答案:使用java自带的jstack工具,分析项目的线程日志,jstack工具如何使用,该篇文章已介绍过《技术自查番外篇四:Jstack与线程》

如何解决死锁

刚才也提到死锁的四个条件,只要打破任意一条件,解决起来就容易。

大体分为两种方法

1. 资源有序分配法(适用于synchronize和lock)

2. 轮询法(仅适用于lock)

一、资源有序分配法

简单概括:线程之间保证获取锁的顺序是一致。例如:线程A和线程B,以同样的顺序获取锁A和锁B(适用于synchronize和lock,这里以synchronize为实例)

示例代码

/**
 * @author Leo
 * @description
 * @createDate 2021/9/4 21:16
 **/
public class DieDemo {

    public static void main(String[] args) {
        DieLock d1 = new DieLock();
        DieLock d2 = new DieLock();
        Thread t1 = new Thread(d1);
        Thread t2 = new Thread(d2);
        t1.start();
        t2.start();
    }

}
/**
 * @author Leo
 * @description
 * @createDate 2021/8/30 22:21
 **/
public class DieLock implements Runnable {

    @Override
    public void run() {
        while (true) {
            synchronized (MyLock.obj1) {
                System.out.println(Thread.currentThread().getName() + "....if...obj1..." );
                synchronized (MyLock.obj2) {
                    System.out.println(Thread.currentThread().getName() + ".....if.....obj2....." );

                }
            }
        }
    }
}
/**
 * @author Leo
 * @description
 * @createDate 2021/8/30 22:22
 **/
public class MyLock {
    public static Object obj1 = new Object();
    public static Object obj2 = new Object();
}
日志
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
Thread-0.....if.....obj2.....
Thread-0....if...obj1...
................

二、轮询法

简单概括:轮询获取锁的状态(仅支持lock)

因为lock支持非阻塞式获取锁,所以轮询法仅支持lock

/**
 * @author Leo
 * @description lock版 trylock 优化版2,轮询
 * @createDate 2021/9/16 23:13
 **/
public class NoDieLockLock2 {

    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // 创建锁 A
        Lock lockB = new ReentrantLock(); // 创建锁 B

        // 创建线程 1(使用轮询锁)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用轮询锁
                pollingLock(lockA, lockB);
            }
        });
        t1.start(); // 运行线程

        // 创建线程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lockB.lock(); // 加锁
                System.out.println("线程 2:获取到锁 B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 2:等待获取 A...");
                    lockA.lock(); // 加锁
                    try {
                        System.out.println("线程 2:获取到锁 A!");
                    } finally {
                        lockA.unlock(); // 释放锁
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 线程 2 忘记释放锁资源
                    // lockB.unlock(); // 释放锁
                }
            }
        });
        t2.start(); // 运行线程
    }

    /**
     * 轮询锁
     * <p>
     */
    public static void pollingLock(Lock lockA, Lock lockB) {
        // 轮询次数计数器
        while (true) {
            if (lockA.tryLock()) { // 尝试获取锁
                System.out.println("线程 1:获取到锁 A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 1:等待获取 B...");
                    if (lockB.tryLock()) { // 尝试获取锁
                        try {
                            System.out.println("线程 1:获取到锁 B!");
                        } finally {
                            lockB.unlock(); // 释放锁
                            System.out.println("线程 1:释放锁 B.");
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); // 释放锁
                    System.out.println("线程 1:释放锁 A.");
                }
            }

            // 等待一秒再继续尝试获取锁
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

上述代码由于trylock会一致轮询锁的状态,这样也会造成很大的资源开销,为了解决这问题,我们可以优化下,加个最大的轮询次数

优化1

/**
 * @author Leo
 * @description lock版 trylock 优化版1,轮询加次数限制
 * @createDate 2021/9/16 23:13
 **/
public class NoDieLockLock2 {

    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // 创建锁 A
        Lock lockB = new ReentrantLock(); // 创建锁 B

        // 创建线程 1(使用轮询锁)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用轮询锁
                pollingLock(lockA, lockB, 3);
            }
        });
        t1.start(); // 运行线程

        // 创建线程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lockB.lock(); // 加锁
                System.out.println("线程 2:获取到锁 B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 2:等待获取 A...");
                    lockA.lock(); // 加锁
                    try {
                        System.out.println("线程 2:获取到锁 A!");
                    } finally {
                        lockA.unlock(); // 释放锁
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 线程 2 忘记释放锁资源
                    // lockB.unlock(); // 释放锁
                }
            }
        });
        t2.start(); // 运行线程
    }

    /**
     * 轮询锁
     * <p>
     * maxCount:最大轮询次数
     */
    public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
        // 轮询次数计数器
        int count = 0;
        while (true) {
            if (lockA.tryLock()) { // 尝试获取锁
                System.out.println("线程 1:获取到锁 A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 1:等待获取 B...");
                    if (lockB.tryLock()) { // 尝试获取锁
                        try {
                            System.out.println("线程 1:获取到锁 B!");
                        } finally {
                            lockB.unlock(); // 释放锁
                            System.out.println("线程 1:释放锁 B.");
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); // 释放锁
                    System.out.println("线程 1:释放锁 A.");
                }
            }

            // 判断是否已经超过最大次数限制
            if (count++ > maxCount) {
                // 终止循环
                System.out.println("轮询锁获取失败,记录日志或执行其他失败策略");
                return;
            }

            // 等待一秒再继续尝试获取锁
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

优化2,轮询+次数+随机时间

/**
 * @author Leo
 * @description lock版 trylock 优化版4,轮询加次数限制 + 随机睡眠时间
 * @createDate 2021/9/16 23:13
 **/
public class NoDieLockLock4 {

    private static Random rdm = new Random();

    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // 创建锁 A
        Lock lockB = new ReentrantLock(); // 创建锁 B

        // 创建线程 1(使用轮询锁)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用轮询锁
                pollingLock(lockA, lockB, 3);
            }
        });
        t1.start(); // 运行线程

        // 创建线程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lockB.lock(); // 加锁
                System.out.println("线程 2:获取到锁 B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 2:等待获取 A...");
                    lockA.lock(); // 加锁
                    try {
                        System.out.println("线程 2:获取到锁 A!");
                    } finally {
                        lockA.unlock(); // 释放锁
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 线程 2 忘记释放锁资源
                    // lockB.unlock(); // 释放锁
                }
            }
        });
        t2.start(); // 运行线程
    }

    /**
     * 轮询锁
     * <p>
     * maxCount:最大轮询次数
     */
    public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
        // 轮询次数计数器
        int count = 0;
        while (true) {
            if (lockA.tryLock()) { // 尝试获取锁
                System.out.println("线程 1:获取到锁 A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 1:等待获取 B...");
                    if (lockB.tryLock()) { // 尝试获取锁
                        try {
                            System.out.println("线程 1:获取到锁 B!");
                        } finally {
                            lockB.unlock(); // 释放锁
                            System.out.println("线程 1:释放锁 B.");
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); // 释放锁
                    System.out.println("线程 1:释放锁 A.");
                }
            }

            // 判断是否已经超过最大次数限制
            if (count++ > maxCount) {
                // 终止循环
                System.out.println("轮询锁获取失败,记录日志或执行其他失败策略");
                return;
            }

            // 等待一秒再继续尝试获取锁
            try {
                Thread.sleep(300 + rdm.nextInt(8) * 100); // 固定时间 + 随机时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值