线程死锁是指两个或两个以上的线程在执行过程中,因争夺资源而互相等待对方释放锁,导致所有线程都无法继续执行的状态。在Java多线程开发中,死锁是非常常见的并发问题,一旦出现会导致程序部分功能失效甚至整体卡死,需要开发者掌握对应的处理方案。

线程死锁的产生原因
死锁的产生需要满足四个必要条件,只要破坏其中任意一个条件,就可以避免死锁:
- 互斥条件:资源同一时间只能被一个线程占用,比如被synchronized修饰的代码块同一时间只能有一个线程进入。
- 请求并持有条件:线程已经持有了至少一个资源,同时又提出了新的资源请求,而新资源被其他线程占用,此时线程不会释放已持有的资源。
- 不可剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程强行剥夺,只能由持有资源的线程主动释放。
- 循环等待条件:存在一种线程资源的循环等待关系,比如线程A等待线程B持有的资源,线程B等待线程A持有的资源。
如何检测Java中的线程死锁
当程序出现疑似死锁的情况时,可以通过以下方式检测:
使用JDK自带工具检测
JDK提供了jstack、jconsole等工具可以查看线程状态,定位死锁:
- 先通过jps命令获取运行中的Java进程ID。
- 使用jstack 进程ID命令打印线程栈信息,如果输出中包含Found one Java-level deadlock字样,就说明存在死锁,同时会给出死锁相关的线程和资源信息。
代码中主动检测死锁
可以通过ThreadMXBean获取线程死锁信息,示例代码如下:
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
public class DeadlockDetector {
public static void detectDeadlock() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 获取死锁线程的ID数组
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
System.out.println("检测到死锁线程,线程ID如下:");
for (long threadId : deadlockedThreads) {
System.out.println(threadId);
}
} else {
System.out.println("未检测到死锁");
}
}
}
处理线程死锁的常用方案
调整锁的获取顺序
破坏循环等待条件是最常用的避免死锁的方式,只要所有线程都按照相同的顺序获取锁,就不会出现循环等待。比如两个线程都需要获取锁A和锁B,那么统一规定先获取锁A再获取锁B,就可以避免死锁。
以下是错误和正确的锁获取顺序示例:
// 错误示例:两个线程获取锁的顺序不同,可能产生死锁
class WrongLockOrder {
private final Object lockA = new Object();
private final Object lockB = new Object();
// 线程1调用的方法,先获取lockA再获取lockB
public void method1() {
synchronized (lockA) {
System.out.println("线程1获取lockA");
synchronized (lockB) {
System.out.println("线程1获取lockB");
}
}
}
// 线程2调用的方法,先获取lockB再获取lockA,可能产生死锁
public void method2() {
synchronized (lockB) {
System.out.println("线程2获取lockB");
synchronized (lockA) {
System.out.println("线程2获取lockA");
}
}
}
}
// 正确示例:统一锁获取顺序,先获取lockA再获取lockB
class CorrectLockOrder {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void method1() {
synchronized (lockA) {
System.out.println("线程1获取lockA");
synchronized (lockB) {
System.out.println("线程1获取lockB");
}
}
}
public void method2() {
// 同样先获取lockA再获取lockB,避免死锁
synchronized (lockA) {
System.out.println("线程2获取lockA");
synchronized (lockB) {
System.out.println("线程2获取lockB");
}
}
}
}
设置锁超时时间
使用显式锁ReentrantLock的tryLock方法可以尝试获取锁,如果指定时间内没有获取到锁就放弃,避免无限等待。这样可以破坏请求并持有条件,避免死锁。
示例代码如下:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
class LockTimeoutExample {
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
public void method1() {
try {
// 尝试获取lockA,最多等待1秒
if (lockA.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("线程1获取lockA成功");
// 尝试获取lockB,最多等待1秒
if (lockB.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("线程1获取lockB成功,执行逻辑");
} else {
System.out.println("线程1获取lockB失败,释放lockA");
}
} else {
System.out.println("线程1获取lockA失败");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放已获取的锁
if (lockA.isHeldByCurrentThread()) {
lockA.unlock();
}
if (lockB.isHeldByCurrentThread()) {
lockB.unlock();
}
}
}
public void method2() {
try {
if (lockA.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("线程2获取lockA成功");
if (lockB.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("线程2获取lockB成功,执行逻辑");
} else {
System.out.println("线程2获取lockB失败,释放lockA");
}
} else {
System.out.println("线程2获取lockA失败");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lockA.isHeldByCurrentThread()) {
lockA.unlock();
}
if (lockB.isHeldByCurrentThread()) {
lockB.unlock();
}
}
}
}
使用并发工具类替代手动加锁
Java并发包提供了很多线程安全的工具类,比如ConcurrentHashMap、CountDownLatch、CyclicBarrier等,这些工具类内部已经做好了并发控制,不需要开发者手动加锁,可以从根源上减少死锁的发生概率。
比如使用ConcurrentHashMap存储共享数据,就不需要使用synchronized对操作加锁:
import java.util.concurrent.ConcurrentHashMap;
class ConcurrentToolExample {
// 线程安全的Map,不需要手动加锁
private final ConcurrentHashMap<String, Integer> dataMap = new ConcurrentHashMap<>();
public void addData(String key, Integer value) {
// put方法是线程安全的,不会出现并发问题
dataMap.put(key, value);
}
public Integer getData(String key) {
return dataMap.get(key);
}
}
减小锁的粒度
锁的粒度越小,线程持有锁的时间就越短,发生死锁的概率就越低。比如在操作共享数据时,只对有修改操作的部分加锁,不需要对整个方法加锁,这样可以让其他线程更快获取到锁,减少等待时间。
死锁发生后的处理方案
如果已经在生产环境发生了死锁,首先可以通过jstack等工具定位死锁的线程和代码位置,然后重启对应的服务释放死锁状态。之后再根据定位到的代码,按照上述的避免死锁方案修改代码,重新发布版本,避免死锁再次出现。
需要注意的是,死锁问题很难通过测试完全覆盖,因为死锁的发生往往和线程执行的时序有关,所以开发阶段就要做好锁的设计,从源头避免死锁的产生。
Java多线程线程死锁死锁处理synchronized修改时间:2026-06-28 10:30:37