java中什么是死锁?如何避免它?

什么是死锁?

在 Java 中,死锁(Deadlock)是指两个或多个线程相互等待对方释放锁,从而导致线程永远无法继续执行的一种状态。死锁通常发生在多线程环境中,当线程需要获取多个锁时,锁的获取顺序不一致可能导致循环等待。
死锁的特征

    互斥:每个资源在同一时刻只能被一个线程占用。
    占有和等待:线程已持有至少一个资源,同时等待获取另一个资源。
    不可剥夺:线程不能强行剥夺其他线程已持有的资源。
    循环等待:存在线程-资源的循环等待链。

死锁的示例

以下是一个经典的死锁示例:

public class DeadlockExample {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1: Holding lock 1...");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                System.out.println("Thread 1: Waiting for lock 2...");
                synchronized (lock2) {
                    System.out.println("Thread 1: Acquired lock 2.");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: Holding lock 2...");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                System.out.println("Thread 2: Waiting for lock 1...");
                synchronized (lock1) {
                    System.out.println("Thread 2: Acquired lock 1.");
                }
            }
        });

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

在此示例中:

    thread1 先获取 lock1,然后尝试获取 lock2。
    thread2 先获取 lock2,然后尝试获取 lock1。
    如果两个线程同时进入等待,程序会发生死锁。

如何避免死锁?
1. 避免嵌套锁

减少嵌套锁的使用,尽量让线程一次只持有一个锁。这样可以显著降低发生死锁的可能性。
2. 保持锁的获取顺序

确保所有线程按照相同的顺序获取锁。例如:

public void safeMethod() {
    synchronized (lock1) {
        synchronized (lock2) {
            // 临界区代码
        }
    }
}

所有线程都按照 lock1 -> lock2 的顺序获取锁,就可以避免循环等待。
3. 使用 tryLock(ReentrantLock)

Java 提供的 java.util.concurrent.locks.ReentrantLock 可以使用 tryLock() 方法尝试获取锁。如果无法获取锁,可以避免阻塞并采取其他操作。

示例代码:

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

public class AvoidDeadlockExample {
    private static final Lock lock1 = new ReentrantLock();
    private static final Lock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            if (lock1.tryLock()) {
                try {
                    System.out.println("Thread 1: Acquired lock 1");
                    Thread.sleep(100);
                    if (lock2.tryLock()) {
                        try {
                            System.out.println("Thread 1: Acquired lock 2");
                        } finally {
                            lock2.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock1.unlock();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            if (lock2.tryLock()) {
                try {
                    System.out.println("Thread 2: Acquired lock 2");
                    Thread.sleep(100);
                    if (lock1.tryLock()) {
                        try {
                            System.out.println("Thread 2: Acquired lock 1");
                        } finally {
                            lock1.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock2.unlock();
                }
            }
        });

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

4. 超时机制

使用带超时的锁获取方法,例如 tryLock(long timeout, TimeUnit unit),避免无限等待。
5. 尽量使用更高层次的并发工具

Java 的并发工具类(如 java.util.concurrent 中的 ExecutorService、Semaphore 等)设计良好,减少了直接使用锁的复杂性,从而降低了死锁的风险。
6. 检测死锁

可以使用工具检测死锁,例如:

    Java 的 jstack 工具可以定位死锁。
    在代码中使用 ThreadMXBean 检测线程死锁。

请使用浏览器的分享功能分享到微信等