java 中什么是死锁?

死锁是多线程竞争资源导致的互相等待现象,需满足互斥、占有等待、不可剥夺和循环等待四个条件;Java中常见于线程以不同顺序获取多个锁,如线程1持lock1等lock2,线程2持lock2等lock1;可通过按序加锁、设置超时、减少锁范围、避免嵌套锁及使用jstack工具检测来有效预防。

死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,导致这些线程都无法继续执行下去。

死锁发生的条件

死锁的产生必须同时满足以下四个条件,缺一不可:

  • 互斥条件:某个资源一次只能被一个线程占用。
  • 占有并等待:线程已经持有了至少一个资源,但又申请新的资源,而该资源被其他线程占用,此时该线程阻塞,但不释放已持有的资源。
  • 不可剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行抢占。
  • 循环等待条件:存在一个线程和资源的循环等待链,比如线程 A 等待线程 B 占用的资源,线程 B 又等待线程 A 占用的资源。

Java 中死锁的常见场景

在 Java 多线程编程中,死锁通常发生在多个线程以不同的顺序获取多个锁。例如:

Object lock1 = new Object();
Object lock2 = new Object();

// 线程1
new Thread(() -> {
    synchro

nized (lock1) { System.out.println("Thread1 获取 lock1"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock2) { System.out.println("Thread1 获取 lock2"); } } }).start(); // 线程2 new Thread(() -> { synchronized (lock2) { System.out.println("Thread2 获取 lock2"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock1) { System.out.println("Thread2 获取 lock1"); } } }).start();

在这个例子中,线程1先拿 lock1 再申请 lock2,线程2先拿 lock2 再申请 lock1。如果两个线程几乎同时运行,就可能造成线程1持有 lock1 等 lock2,线程2持有 lock2 等 lock1,从而形成死锁。

如何避免死锁

可以通过以下方式预防或避免死锁:

  • 按顺序加锁:所有线程以相同的顺序请求资源。例如都先获取 lock1 再获取 lock2。
  • 使用超时机制:尝试使用 tryLock(long timeout) 方法,在指定时间内无法获取锁就放弃,避免无限等待。
  • 减少锁的范围:尽量缩短同步代码块,只在必要时才持有锁。
  • 避免嵌套锁:尽量不要在一个 synchronized 块中再去获取另一个锁。
  • 使用工具检测:利用 jstack 等工具分析线程堆栈,排查死锁问题。
基本上就这些。死锁虽然不容易完全杜绝,但通过合理设计和编码规范可以有效规避。