探秘死锁:原理、发生条件及解决方案

探秘死锁:原理、发生条件及解决方案

死锁是多线程编程中常见的一个问题,它会导致程序停止响应,进而影响系统的稳定性和性能。理解死锁的原理、发生条件以及如何预防和解决死锁是编写健壮并发程序的关键。

1. 死锁的定义

死锁是指两个或多个线程在执行过程中因争夺资源而相互等待,从而使得这几个线程都无法继续执行。死锁会导致系统资源无法被正常利用,进而影响系统的稳定性。

如上图所示死锁状态,线程 A 己经持有了资源 2,它同时还想申请资源 1,可是此时线程 B 已经持有了资源 1 ,线程 A 只能等待。

反观线程 B 持有了资源 1 ,它同时还想申请资源 2,但是资源 2 已经被线程 A 持有,线程 B 只能等待。所以线程 A 和线程 B 就因为相互等待对方已经持有的资源,而进入了死锁状态。

2. 死锁的四个必要条件

根据Coffman提出的经典死锁四个必要条件(又称Coffman条件):

  • 互斥(Mutual Exclusion):资源不能被多个线程同时使用。
  • 持有并等待(Hold and Wait):一个线程已经持有了至少一个资源,同时又在等待获取额外的资源。
  • 不可剥夺(No Preemption):线程获得的资源在使用完之前不能被强行剥夺,只能由线程自己释放。
  • 循环等待(Circular Wait):存在一个循环等待链,即每个线程都在等待下一个线程所持有的资源。

3. 死锁的示例代码

以下是一个Java示例,展示了死锁的产生:

public class DeadlockExample {
    private static final Object resource1 = new Object();
    private static final Object resource2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 1: locked resource 1");
                try { Thread.sleep(50); } catch (InterruptedException e) {}
                synchronized (resource2) {
                    System.out.println("Thread 1: locked resource 2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println("Thread 2: locked resource 2");
                try { Thread.sleep(50); } catch (InterruptedException e) {}
                synchronized (resource1) {
                    System.out.println("Thread 2: locked resource 1");
                }
            }
        });

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

在这个示例中,thread1首先锁住resource1,然后试图锁住resource2。与此同时,thread2首先锁住resource2,然后试图锁住resource1。这会导致两个线程相互等待,从而产生死锁。

4. 死锁的预防和避免

破坏互斥条件
  • 使资源尽量支持共享访问,例如使用读写锁来允许多个线程同时读取资源。
破坏持有并等待条件
  • 线程在开始时一次性请求所需要的所有资源,如果没有得到全部资源,则释放已获得的资源并重新请求。
  • 使用锁定机制时,可以尝试锁定时限,如果超时则释放已持有的锁。
破坏不可剥夺条件
  • 设计资源请求策略,使得可以强制抢占资源。例如,使用可重入锁(ReentrantLock)和条件变量(Condition)来支持资源的强制释放。
破坏循环等待条件
  • 为资源分配一个全局顺序,线程按照固定顺序请求资源,避免形成循环等待链。

5. 死锁检测与恢复

死锁检测
  • 系统可以定期检测资源分配图,判断是否存在循环等待。如果发现死锁,则需要进行相应的恢复操作。
死锁恢复
  • 资源抢占:强制从某个线程中剥夺资源,分配给其他需要资源的线程。
  • 回滚:回滚部分或全部死锁进程的操作,使其释放所持有的资源。
  • 终止进程:直接终止部分或全部死锁进程,从而释放资源。

示例代码中的解决方案

以下是改进后的代码,避免了死锁:

public class DeadlockFreeExample {
    private static final Object resource1 = new Object();
    private static final Object resource2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 1: locked resource 1");
                try { Thread.sleep(50); } catch (InterruptedException e) {}
                synchronized (resource2) {
                    System.out.println("Thread 1: locked resource 2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource1) { // 改变锁顺序
                System.out.println("Thread 2: locked resource 1");
                try { Thread.sleep(50); } catch (InterruptedException e) {}
                synchronized (resource2) {
                    System.out.println("Thread 2: locked resource 2");
                }
            }
        });

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

在这个改进后的例子中,我们确保了两个线程都以相同的顺序(先锁resource1,再锁resource2)来获取锁,从而避免了循环等待的条件。

结论

理解死锁的原理及其四个必要条件是预防和解决死锁问题的基础。通过合理的设计和编程技巧,可以有效地避免死锁,提高并发程序的健壮性和性能。

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jack_hrx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值