Java 多线程(三)—— 死锁

在多线程编程中,死锁是一个常见且令人头疼的问题。死锁发生在两个或多个线程相互等待对方持有的资源,导致所有线程都无法继续执行。为了更好地理解死锁,我们首先来看看什么是死锁,以及如何在 Java 中避免死锁。

死锁的产生

死锁通常发生在以下四个条件同时满足时:

  1. 互斥条件:至少有一个资源必须被一个线程独占。
  2. 保持与等待:一个线程持有至少一个资源,并等待获取其他资源。
  3. 不剥夺条件:线程已经获得的资源在完成之前不能被剥夺。
  4. 循环等待条件:存在一个线程的循环等待资源的情况。

代码示例

下面的代码演示了一个简单的死锁场景。在这个例子中,我们有两个线程,线程 A 和线程 B,它们分别持有两个锁 lock1lock2

public class DeadlockExample {

    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread A: Holding lock 1...");

                // 睡眠一段时间以确保线程 B 获取 lock 2
                try { Thread.sleep(100); } catch (InterruptedException e) {}

                System.out.println("Thread A: Waiting for lock 2...");
                synchronized (lock2) {
                    System.out.println("Thread A: Acquired lock 2!");
                }
            }
        });

        Thread threadB = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread B: Holding lock 2...");

                // 睡眠一段时间以确保线程 A 获取 lock 1
                try { Thread.sleep(100); } catch (InterruptedException e) {}

                System.out.println("Thread B: Waiting for lock 1...");
                synchronized (lock1) {
                    System.out.println("Thread B: Acquired lock 1!");
                }
            }
        });

        threadA.start();
        threadB.start();
    }
}

在以上代码中,threadA 先获取 lock1,然后尝试获取 lock2;而 threadB 则先获取 lock2,然后尝试获取 lock1。由于两者在相互等待对方持有的锁,程序最终会陷入死锁状态,两个线程将永远互相等待下去。

如何避免死锁

  1. ** Lock Ordering(锁顺序)**:确保所有线程按照相同的顺序请求锁。比如,始终先获取 lock1,再获取 lock2,这样可以避免循环等待的情况。
public class AvoidDeadlock {

    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread A: Holding lock 1...");

                synchronized (lock2) {
                    System.out.println("Thread A: Acquired lock 2!");
                }
            }
        });

        Thread threadB = new Thread(() -> {
            synchronized (lock1) {  // 先获取 lock1
                System.out.println("Thread B: Holding lock 1...");

                synchronized (lock2) {  // 然后获取 lock2
                    System.out.println("Thread B: Acquired lock 2!");
                }
            }
        });

        threadA.start();
        threadB.start();
    }
}
  1. 使用时间限制:为锁的请求设置超时机制,如果无法在规定时间内获取锁,则放弃请求,从而减少死锁的可能性。

  2. 避免持有锁的时间过长:尽量减少锁的持有时间,确保在持有锁时尽快完成操作,并释放锁。

  3. 使用更高层次的抽象:使用 Java 提供的并发工具类,如 ReentrantLockSemaphore 等,它们通常会提供更好的死锁控制策略。

结论

死锁是多线程编程中的一个重要问题,可以通过设计良好的锁策略、合理的顺序和超时机制来有效避免。理解死锁的发生机制和预防措施,对于提高 Java 并发程序的稳定性和性能至关重要。希望通过本篇文章的讲解,能帮助开发者更深入地理解死锁及其解决方案。

点赞(0) 打赏

微信小程序

微信扫一扫体验

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部