Java进阶篇--死锁

目录

导致死锁的原因

避免死锁的方法

代码示例


死锁是指两个或多个线程在互相等待对方释放资源的情况下,无法继续执行的状态。当发生死锁时,线程将永远阻塞,程序也无法正常完成。

导致死锁的原因

死锁是多线程编程中的一种常见问题,它发生在两个或多个线程彼此持有对方所需资源的情况下,导致它们都无法继续执行。这些线程被称为相互等待对方的资源,从而形成了死锁状态。

导致死锁的原因通常可以归结为以下四个必要条件的同时满足:

  1. 互斥条件(Mutual Exclusion):一个资源每次只能被一个线程占用。当一个线程获得了资源的独占权后,其他线程就无法再访问该资源,直到该线程释放资源。
  2. 请求与保持条件(Hold and Wait):一个线程在持有了至少一个资源的同时又请求其他资源,而这些资源被其他线程所占用。当这种情况发生时,如果不能立即获得所需的资源,就会阻塞并等待其他线程释放资源。
  3. 不可剥夺条件(No Preemption):资源只能在线程自愿释放时才能被其他线程占用,其他线程无法强行将其剥夺。即线程需要使用完所有获取到的资源后才会主动释放资源,不会被其他线程打断。
  4. 循环等待条件(Circular Wait):存在多个线程之间形成一个环形链,每个线程都在等待下一个线程所持有的资源。例如,线程 A 等待线程 B 持有的资源,线程 B 等待线程 C 持有的资源,以此类推,直到线程 N 等待线程 A 持有的资源。

只有当以上四个条件同时满足时,死锁才会发生。解决死锁问题的方法通常包括破坏其中一个或多个必要条件。

避免死锁的方法

避免死锁是多线程编程中重要的问题之一,下面介绍几种常见的避免死锁的方法:

  1. 加锁顺序:确保所有线程以相同的顺序获得锁。通过强制要求线程按照特定的顺序获取资源锁,可以避免循环等待条件的发生。
  2. 加锁时限:对获取不到所需资源的线程设置一个超时时间,在超过一定时间后如果仍未获取到资源,则释放已经占用的资源,避免持有并等待条件的发生。这种方法需要谨慎使用,需要根据具体场景来确定超时时间的合理性,并避免引入新的问题。
  3. 死锁检测:实时监控程序运行状态,检测是否存在死锁。一旦检测到死锁,系统可以采取一些恢复措施,例如强制释放某些资源或者重启线程等。
  4. 资源分配策略:通过合理的资源分配策略来预防死锁。例如,银行家算法(Banker's Algorithm)用于在分布式系统中避免死锁,通过动态分配资源并根据系统需要进行资源回收。
  5. 避免持有并等待条件:线程在请求新的资源之前释放已占有的资源。这可以通过设计合适的资源分配算法来实现,例如按照资源请求层级来分配资源,或者采用资源预先分配的方式。
  6. 使用互斥量和条件变量:互斥量和条件变量是常用的同步机制,在使用时需要合理地获取和释放锁,以遵循加锁顺序,避免死锁的发生。

需要注意的是,以上方法并非适用于所有情况,具体的应用需要根据具体的需求和场景来选择合适的策略。在设计多线程程序时,合理的资源管理和同步机制是避免死锁问题的关键。同时,定期进行代码审查、测试和性能优化也是保证程序健壮性和可靠性的重要手段。

代码示例

以下是同时包含死锁和避免死锁的代码示例,

public class main {
    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) { // 获取资源1的锁
                System.out.println("线程 1:持有资源 1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 1:等待资源 2");
                synchronized (resource2) { // 尝试获取资源2的锁
                    System.out.println("线程 1:持有资源 1 和资源 2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource1) { // 尝试获取资源1的锁
                System.out.println("线程 2:持有资源 1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 2:等待资源 2");
                synchronized (resource2) { // 获取资源2的锁
                    System.out.println("线程 2:持有资源 1 和资源 2");
                }
            }
        });

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

        // 等待线程执行完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 避免死锁的示例
        Object resourceA = new Object();
        Object resourceB = new Object();

        Thread thread3 = new Thread(() -> {
            synchronized (resourceA) { // 获取资源A的锁
                System.out.println("线程 3:持有资源 A");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 3:等待资源 B");
                synchronized (resourceB) { // 尝试获取资源B的锁
                    System.out.println("线程 3:持有资源 A 和资源 B");
                }
            }
        });

        Thread thread4 = new Thread(() -> {
            synchronized (resourceA) { // 获取资源A的锁
                System.out.println("线程 4:持有资源 A");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 4:等待资源 B");
                synchronized (resourceB) { // 尝试获取资源B的锁
                    System.out.println("线程 4:持有资源 A 和资源 B");
                }
            }
        });

        thread3.start();
        thread4.start();
    }
}

在这个例子中,一开始的两个线程 thread1 和 thread2 尝试获取 resource1 和 resource2 的锁以形成死锁。为了避免死锁,我们在这两个线程中修改了获取资源的顺序。其中一个线程先获取 resource1,另一个线程再获取 resource1,这样能够保证资源的获取顺序是一致的,避免了循环等待。

在后面的代码示例中,我们通过尝试获取不同的资源来模拟死锁情况。为了避免死锁,我们采取了避免持有并请求条件的策略,即一个线程只能在释放所有资源之后再请求新的资源。这样能够确保资源的占用和释放是一致的,不会出现死锁的情况。

注意,在上面的示例中,为了能够观察到死锁和避免死锁的效果,我们需要等待线程执行完成。因此,我们在两个线程启动之后使用 join() 方法来等待它们执行完成。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

正在奋斗的程序猿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值