解释死锁的概念,并给出一个死锁发生的例子。

参考回答

死锁是指两个或多个线程在运行过程中,因竞争资源而相互等待,导致线程永远无法继续执行的状态。换句话说,死锁发生时,线程 A 等待线程 B 持有的资源,而线程 B 同时等待线程 A 持有的资源,造成循环等待。

死锁的四个必要条件(”不可缺少条件”):

  1. 互斥条件:某些资源(如锁)在某一时刻只能由一个线程占用。
  2. 占有并等待:一个线程占有资源的同时,还在等待其他线程占有的资源。
  3. 不可抢占:资源不能被强制抢占,只能由线程自己释放。
  4. 循环等待:多个线程之间形成一个环状的资源等待关系。

详细讲解与拓展

死锁的例子

代码示例:

class Resource {
    private final String name;

    public Resource(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public class DeadlockExample {
    public static void main(String[] args) {
        Resource resourceA = new Resource("ResourceA");
        Resource resourceB = new Resource("ResourceB");

        Thread thread1 = new Thread(() -> {
            synchronized (resourceA) {
                System.out.println(Thread.currentThread().getName() + " locked " + resourceA.getName());

                try {
                    Thread.sleep(100); // 模拟一些工作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resourceB) {
                    System.out.println(Thread.currentThread().getName() + " locked " + resourceB.getName());
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resourceB) {
                System.out.println(Thread.currentThread().getName() + " locked " + resourceB.getName());

                try {
                    Thread.sleep(100); // 模拟一些工作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resourceA) {
                    System.out.println(Thread.currentThread().getName() + " locked " + resourceA.getName());
                }
            }
        });

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

可能输出:

Thread-0 locked ResourceA
Thread-1 locked ResourceB

解释:

  1. 线程 1 持有 resourceA,试图获取 resourceB 的锁。
  2. 线程 2 持有 resourceB,试图获取 resourceA 的锁。
  3. 由于两个线程都在等待对方释放锁,形成了死锁,程序无法继续执行。

如何检测和避免死锁?

1. 避免死锁的策略
  • 避免循环等待条件: 为所有线程获取锁的顺序规定一个一致的顺序。按固定顺序获取资源可以有效避免死锁。
    synchronized (resourceA) {
      synchronized (resourceB) {
          // 按固定顺序锁定资源
      }
    }
    
  • 尝试锁定超时机制: 使用 tryLock() 方法,可以避免长时间等待锁。
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class DeadlockAvoidance {
      private static final Lock lock1 = new ReentrantLock();
      private static final Lock lock2 = new ReentrantLock();
    
      public static void main(String[] args) {
          Thread thread1 = new Thread(() -> {
              try {
                  if (lock1.tryLock()) {
                      try {
                          Thread.sleep(100);
                          if (lock2.tryLock()) {
                              try {
                                  System.out.println("Thread 1: Locked both resources");
                              } finally {
                                  lock2.unlock();
                              }
                          }
                      } finally {
                          lock1.unlock();
                      }
                  }
              } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
              }
          });
    
          Thread thread2 = new Thread(() -> {
              try {
                  if (lock2.tryLock()) {
                      try {
                          Thread.sleep(100);
                          if (lock1.tryLock()) {
                              try {
                                  System.out.println("Thread 2: Locked both resources");
                              } finally {
                                  lock1.unlock();
                              }
                          }
                      } finally {
                          lock2.unlock();
                      }
                  }
              } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
              }
          });
    
          thread1.start();
          thread2.start();
      }
    }
    
  • 减少锁的粒度: 尽可能减少锁定的资源和范围,降低锁冲突的概率。


2. 检测死锁

使用 jstack 工具来检测 Java 应用程序中是否存在死锁。

步骤:

  1. 获取运行 Java 程序的进程 ID(jps 命令)。
  2. 使用 jstack <PID> 分析线程堆栈信息。
  3. 检查是否有 Found one Java-level deadlock 的信息。

总结:死锁的四个必要条件

条件 描述
互斥条件 每个资源在同一时刻只能被一个线程占用。
占有并等待 一个线程已持有一个资源,但还在等待其他线程持有的资源。
不可抢占 资源不能被强制剥夺,只能由占有它的线程主动释放。
循环等待 线程间形成一个环状的等待资源链,每个线程都在等待下一个线程持有的资源。

发表评论

后才能评论