线程死锁的原因

造成线程死锁的原因以及解决方案

造成死锁的原因

一个线程一把锁

这种情况 可重入锁没事,不可重入锁会造成死锁, Java 中 synchronized是可重入锁, 因此没有问题

(所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。)

public class Test {
    static Object locker1 = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            test();
        });
        t1.start();
        t1.join();
        System.out.println("没有造成死锁!");
    }
    public static void test() {
        // 多个锁情况
        synchronized (locker1) {
            synchronized (locker1){
                System.out.println("执行测试方法");
            }
        }
    }
}

运行结果如下: 使用synchronized 并没有造成死锁情况 但是如果不是synchronized的话,是其他的不可重入锁 就会造成死锁情况

在这里插入图片描述

两个线程两把锁

两个线程两把锁, 此时当两个线程获取两把锁对象的时候顺序并不一样,此时很容易造成死锁

public class Test {
    static Object locker1 = new Object();
    static Object locker2 = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                test();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            test2();
        });
        t2.start();
    }
    public static void test() throws InterruptedException {
        // 多个锁情况
        synchronized (locker1) {
            System.out.println("t1 进入第一把锁!");
            // 为了让线程 2拿到 锁2
            Thread.sleep(1000);
            synchronized (locker2){
                System.out.println("t1 进入第二把锁!");
            }
        }
    }
    public static void test2() {
        synchronized (locker2) {
            System.out.println("t2 进入第一把锁! ");
            synchronized (locker1) {
                System.out.println("t2 进入第二把锁! ");
            }
        }
    }
}

运行结果如下:

线程1和线程2都进入到了第一把锁里面 但是谁都没有释放锁 ,因此导致了线程的死锁情况

其实解决方案很简单, 只需要指定加锁顺序,(外层锁1, 内层锁2) 这样即使 线程1 拿到了外层锁1 , 此时线程 2 拿不到 锁1 就进不去, 就会阻塞等待,直到线程1执行完程序, 释放锁1 和锁2 此时线程2 才能拿到锁1 执行下去.

public class Test {
    static Object locker1 = new Object();
    static Object locker2 = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                test();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            test2();
        });
        t2.start();
        t1.join();
        t2.join();
        System.out.println("没有造成死锁!");
    }
    // 指定两个方法都是外层锁1加锁, 内层锁2 加锁.
    public static void test() throws InterruptedException {
        // 多个锁情况
        synchronized (locker1) {
            System.out.println("t1 进入第一把锁!");
            Thread.sleep(1000);
            synchronized (locker2){
                System.out.println("t1 进入第二把锁!");
            }
        }
    }
    public static void test2() {
        synchronized (locker1) {
            System.out.println("t2 进入第一把锁! ");
            synchronized (locker2) {
                System.out.println("t2 进入第二把锁! ");
            }
        }
    }
}

结果如下:

在这里插入图片描述

n个线程m把锁

举个例子, 就餐问题,如果有五个人(5个线程) 此时筷子也只有五根(5把锁) 但是每个人都需要拿两根筷子才可以就餐,(一个线程调用某个方法需要抢两把锁) 此时如果每个人都拿到了面前的一根筷子,那么谁都吃不了饭, 也就是说, 线程进入了死锁状态!

在这里插入图片描述

因此总结得出 ,死锁的四个必要条件:

  1. 因为线程是抢占式执行,并且锁是互斥使用, 因此一个线程拿到一把锁的时候, 另一个线程就不能使用 (锁的基本特点)
  2. 不可占用: 一个线程拿到了锁,只能够自己执行完方法 ,释放锁资源, 而不能被其他线程强行占有
  3. 请求和保持, 在一个方法内,如果线程拿到一把锁之后,还会想着抢第二把锁,(代码向下执行的特点) 因此,第一把锁在没有执行完该方法,或者在synchronized加锁范围内 是不会释放锁的,并且不断请求获取第二把锁, 从而形成死锁
  4. 循环等待, 相当于加锁顺序不同, “家钥匙锁在车里了, 车钥匙放家里了”.

死锁的解决方案

因此针对与形成死锁的四个必要条件,总结出解决方案:

针对锁进行编号, 如果需要同时获取多把锁的情况,约定加锁顺序, 务必是先对小的编号加锁,后对大的编号加锁

例如 N个线程 M把锁情况:

​ 约定好先获取编号小的筷子, 在获取编号大的筷子 ,并且五个人每个人需要两根筷子,满足只允许4个人同时拿起筷子,那么就不会造成死锁 , 因此此时肯定有一个人能拿到两根筷子,当这个人吃完的时候, 就可以同时释放两个资源,那么就不会造成死锁现象的发生.

例如 两个线程两把锁的情况:

​ 只需要约定好加锁顺序,不构造成线程1拿到锁 1, 线程2拿得到锁2 的情况即可.

每个人需要两根筷子,满足只允许4个人同时拿起筷子,那么就不会造成死锁 , 因此此时肯定有一个人能拿到两根筷子,当这个人吃完的时候, 就可以同时释放两个资源,那么就不会造成死锁现象的发生.

例如 两个线程两把锁的情况:

​ 只需要约定好加锁顺序,不构造成线程1拿到锁 1, 线程2拿得到锁2 的情况即可.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值