代码陷阱:多线程中的死锁危机

欢迎浏览高耳机的博客

希望我们彼此都有更好的收获

感谢三连支持!

你有没有想过,计算机程序也会"堵车"?

当老王忘了带门禁卡时: 

门卫:请刷门禁卡

老王:我需要回家才能拿到门禁卡

门卫:你需要刷门禁卡才能回家

老王:可是我需要回家才能拿到门禁卡

.........


在多线程编程中,死锁是一个常见的问题,它会导致程序的执行效率降低,甚至导致程序完全停止响应。死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种僵局。当线程A持有资源B,等待资源C,而线程B持有资源C,等待资源B时,就会发生死锁。 

死锁的四个必要条件

  1. 互斥条件:资源不能被多个线程同时使用。
  2. 占有和等待条件:线程至少占有一个资源,并且等待获取其他线程占有的资源。
  3. 不可抢占条件:资源只能由占有它的线程自愿释放。
  4. 循环等待条件:存在一个线程的集合,其中每个线程都在等待下一个线程所占有的资源。

代码实例

下面是一个简单的Java代码示例,演示了如何通过synchronized关键字来创建死锁:

public class Demo10 {
    private static Object locker1 = new Object();
    private static Object locker2 = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (locker1) {
                System.out.println("t1 加锁 locker1 完成");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // !!!!!!!!!!!!!!注意,下面的synchronized还在第一个synchronized括号里面
                // 这样才能保证没有释放第一把锁!!!!!!!!!!!!!!!!!!!!!!!!!!
                //thread2中同理
                synchronized (locker2){
                    System.out.println("t1 加锁 locker2 完成");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (locker2) {
                System.out.println("t2 加锁 locker2 完成");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (locker1){
                    System.out.println("t2 加锁 locker1 完成");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

在这个例子中,线程1首先获取locker1的锁,同时线程2则首先获取locker2的锁,线程12同时进入睡眠等待,以确保它们都获得了自己对应的锁,接着它们尝试获取对方已经持有的锁。由于两个线程都在等待对方释放锁,因此它们都将永远等待下去,形成死锁。


解决死锁的策略

虽然死锁是一个复杂的问题,但是我们可以采取一些策略来避免或解决它。以下是一些常见的解决死锁的策略:

  1. 避免占有和等待条件:要求线程在执行前一次性获取所有需要的资源,这样就不会有线程在持有资源的同时等待其他资源。

  2. 资源分配图:使用资源分配图来表示系统中资源的分配情况,通过检测是否存在循环等待来确定是否发生死锁。

  3. 死锁检测算法:系统周期性地检测是否存在死锁,一旦发现死锁,采取措施解除。

  4. 破坏不可抢占条件:允许线程抢占其他线程占有的资源,或者在等待一定时间后自动释放资源。

  5. 使用定时锁:在获取锁时使用超时机制,如果超过指定时间无法获取锁,则放弃并重试。

  6. 顺序加锁:规定所有线程按照相同的顺序获取锁,这样可以避免循环等待条件的发生。

  7. 死锁预防:通过设计来确保死锁的四个必要条件不会同时满足。

  8. 使用并发库:使用Java并发库中的类,如ReentrantLockSemaphore,它们提供了更灵活的锁机制,有助于避免死锁。

下面是一个使用ReentrantLocktryLock方法来避免死锁的代码示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo10 {
    private static final Lock locker1 = new ReentrantLock();
    private static final Lock locker2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            try {
                if (locker1.tryLock()) {
                    System.out.println("t1 加锁 locker1 完成");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (locker2.tryLock()) {
                        System.out.println("t1 加锁 locker2 完成");
                        locker2.unlock();
                    }
                }
            } finally {
                if (locker1.isHeldByCurrentThread()) {
                    locker1.unlock();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                if (locker2.tryLock()) {
                    System.out.println("t2 加锁 locker2 完成");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (locker1.tryLock()) {
                        System.out.println("t2 加锁 locker1 完成");
                        locker1.unlock();
                    }
                }
            } finally {
                if (locker2.isHeldByCurrentThread()) {
                    locker2.unlock();
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

在这个例子中,我们使用了ReentrantLocktryLock方法来尝试获取锁,如果无法获取锁,则线程不会无限期地等待,而是会释放已持有的锁并重试。这样可以有效地避免死锁的发生。

总结

死锁是多线程编程中的一个复杂问题,但是通过理解其产生的原因和条件,我们可以采取有效的策略来避免或解决它。在实际编程中,合理地设计资源的分配和访问策略,是避免死锁的关键。同时,利用现代并发库提供的工具和类,可以帮助我们更好地管理和协调多线程之间的资源访问,从而减少死锁的发生。


希望这篇博客能为你理解多线程编程中的死锁提供一些帮助。

如有不足之处请多多指出。

我是高耳机。

 

 

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值