Java中如何解决死锁 | Java Debug 笔记

504 阅读5分钟

本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看 活动链接

前言

本教程中了例子参考了Java高并发编程(第二版)——葛一鸣


一、锁是什么?

锁一般用于并发编程中保护临界区资源的 举个🌰,同时有多个人去商店买东西,商店中的东西就相当于临界资源,每个人看作一个线程。当某个人去买某件东西时,其他人无法购买的。直到那个人把东西买走释放了锁,商品数量-1,其他人可继续购买。

锁又分为乐观锁和悲观锁

  • 乐观锁认为读多写少,多采用CAS(compare and set)来实现的
  • 悲观锁认为写多读少,遇到并发写的可能性较高。例如synchronized关键字就是用的悲观锁

二、死锁

1.哲学家就餐

假设有五个哲学家👨,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支🥢,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的🥢,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。

约束条件

(1)只有拿到两只🥢时,哲学家才能吃饭。

(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。

(3)任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。

代码如下(示例):

public class DeadLock  extends Thread {
    // 5个筷子
    static Object[] tools = {"k1", "k2", "k3", "k4", "k5"};
    // 位置
    private int i;

    public DeadLock(int i) {
        this.i = i;
        this.setName("哲学家" + i);
    }

    @Override
    public void run() {
        // 先取左筷子
        System.out.println(getName() + "准备吃饭了");
        synchronized (tools[i -1]) {
            try {
                Thread.sleep(50);
                // 再取右筷子
                synchronized (tools[i ==5 ? 0:i]) {
                    Thread.sleep(500);
                    System.out.println(getName() + "吃完了");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        DeadLock d1 = new DeadLock(1);
        DeadLock d2 = new DeadLock(2);
        DeadLock d3 = new DeadLock(3);
        DeadLock d4 = new DeadLock(4);
        DeadLock d5 = new DeadLock(5);
        d1.start();
        d2.start();
        d3.start();
        d4.start();
        d5.start();
        Thread.sleep(1000);
    }
}

一组可能的输出结果如下:

哲学家1准备吃饭了
哲学家3准备吃饭了
哲学家5准备吃饭了
哲学家4准备吃饭了
哲学家2准备吃饭了

很明显此时发生了死锁,每个哲学家都有左筷子,但是无法拿到右筷子。 此时可以通过命令来查看当前Java进程状态

  1. jps

一组可能的输出结果如下:

12613 Jps
12551 DeadLock
3991 Launcher
12552 Launcher
6829 KotlinCompileDaemon
7421 Main
  1. jstack 12551

一组可能的输出结果如下:

......
"哲学家1" #12 prio=5 os_prio=0 cpu=4.44ms elapsed=53.88s tid=0x00007efeac462800 nid=0x3130 waiting for monitor entry  [0x00007efe7a839000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.jackbin.concurrent.chapter04.DeadLock.run(DeadLock.java:26)
        - waiting to lock <0x0000000093bed0c8> (a java.lang.String)
        - locked <0x0000000093bed098> (a java.lang.String)

"哲学家2" #13 prio=5 os_prio=0 cpu=3.73ms elapsed=53.88s tid=0x00007efeac464800 nid=0x3131 waiting for monitor entry  [0x00007efe7a738000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.jackbin.concurrent.chapter04.DeadLock.run(DeadLock.java:26)
        - waiting to lock <0x0000000093bed0f8> (a java.lang.String)
        - locked <0x0000000093bed0c8> (a java.lang.String)

"哲学家3" #14 prio=5 os_prio=0 cpu=3.96ms elapsed=53.88s tid=0x00007efeac466000 nid=0x3132 waiting for monitor entry  [0x00007efe7a637000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.jackbin.concurrent.chapter04.DeadLock.run(DeadLock.java:26)
        - waiting to lock <0x0000000093bed128> (a java.lang.String)
        - locked <0x0000000093bed0f8> (a java.lang.String)
......

2.防止死锁

此处使用ReentrantLock重入锁来防止死锁 具体方案为,如果再5S内拿不到右筷子,则放弃手中的的左筷子,并重新请求

代码如下:

public class DeadLock  extends Thread {
    // 5个筷子
    static ReentrantLock[] tools = {
            new ReentrantLock(),
            new ReentrantLock(),
            new ReentrantLock(),
            new ReentrantLock(),
            new ReentrantLock()
    };
    // 位置
    private int i;

    public DeadLock(int i) {
        this.i = i;
        this.setName("哲学家" + i);
    }

    @Override
    public void run() {
        try {
            while (true) {
                System.out.println(getName() + "准备吃饭了");
                // 先取左筷子
                tools[i-1].lock();
                Thread.sleep(50);
                // 如果5s未取到右筷子,则放弃
                if (tools[i ==5 ? 0:i].tryLock(5, TimeUnit.SECONDS)) {
                    Thread.sleep(500);
                    System.out.println(getName() + "吃完了");
                    // 解锁
                    tools[i-1].unlock();
                    tools[i ==5 ? 0:i].unlock();
                    break;
                } else {
                    System.out.println(getName() + "未取到右筷子,故释放左筷子");
                    tools[i-1].unlock();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        DeadLock d1 = new DeadLock(1);
        DeadLock d2 = new DeadLock(2);
        DeadLock d3 = new DeadLock(3);
        DeadLock d4 = new DeadLock(4);
        DeadLock d5 = new DeadLock(5);
        d1.start();
        d2.start();
        d3.start();
        d4.start();
        d5.start();
        Thread.sleep(1000);
    }
}

一组可能的输出结果如下:

哲学家2准备吃饭了
哲学家4准备吃饭了
哲学家1准备吃饭了
哲学家5准备吃饭了
哲学家3准备吃饭了
哲学家2未取到右筷子,故释放左筷子
哲学家2准备吃饭了
哲学家5未取到右筷子,故释放左筷子
哲学家5准备吃饭了
哲学家4未取到右筷子,故释放左筷子
哲学家4准备吃饭了
哲学家3未取到右筷子,故释放左筷子
哲学家3准备吃饭了
哲学家1未取到右筷子,故释放左筷子
哲学家1准备吃饭了
哲学家2未取到右筷子,故释放左筷子
哲学家2准备吃饭了
哲学家5未取到右筷子,故释放左筷子
哲学家5准备吃饭了
哲学家3未取到右筷子,故释放左筷子
哲学家3准备吃饭了
哲学家1未取到右筷子,故释放左筷子
哲学家1准备吃饭了
哲学家4吃完了
哲学家3吃完了
哲学家2吃完了
哲学家1吃完了
哲学家5吃完了

总结

以上就是我对锁的一些理解🌶,多线程🐮🐸~