凌晨3点,监控告警响个不停,生产系统卡死。查看日志,一行输出都没有,CPU使用率0%,线程全部"僵死"。你遇到了程序员最害怕的噩梦之一——死锁。
一、什么是死锁?一个生动的比喻
想象这个场景:
你在餐厅吃饭,左手拿刀,右手拿叉。 你朋友也来吃饭,但他先拿走了叉,然后伸手来拿你的刀。 你不给刀,因为你需要叉才能吃饭。 他也不给叉,因为他需要刀才能吃饭。
结果:你们俩都吃不上饭,僵持到地老天荒。
这就是死锁!在多线程编程中,当两个或多个线程互相等待对方释放资源时,程序就会永远"卡住"。
二、死锁的"四大必要条件"
死锁不会随便发生,必须同时满足四个条件:
条件1:互斥(Mutual Exclusion)
// 资源一次只能被一个线程使用
public class MutexExample {
private final Object lock = new Object();
public void exclusiveMethod() {
synchronized (lock) { // 互斥访问
// 同一时间只能有一个线程进入
}
}
}
条件2:请求与保持(Hold and Wait)
public class HoldAndWaitExample {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void problematicMethod() {
synchronized (lockA) { // 持有lockA
// ... 一些操作
synchronized (lockB) { // 请求lockB
// 危险!持有lockA的同时请求lockB
}
}
}
}
条件3:不可剥夺(No Preemption)
// 线程已获得的资源不能被强制剥夺
// 在Java中,除非线程主动释放,否则锁会一直被持有
public class NoPreemptionExample {
public void dangerousMethod(Object lock) {
synchronized (lock) {
// 除非这个代码块执行完毕,或者线程被中断
// 否则其他线程无法强制获取这个锁
// 即使是高优先级的线程也不行!
}
}
}
条件4:循环等待(Circular Wait)
// 经典死锁示例
public class CircularWaitExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
// 线程1:先获取lock1,再请求lock2
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("线程1获取lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) { // 等待线程2释放lock2
System.out.println("线程1获取lock2");
}
}
});
// 线程2:先获取lock2,再请求lock1
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("线程2获取lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) { // 等待线程1释放lock1
System.out.println("线程2获取lock1");
}
}
});
t1.start();
t2.start();
// 结果:两个线程永远等待,形成循环等待链
// 线程1 -> 等待lock2 -> 被线程2持有
// 线程2 -> 等待lock1 -> 被线程1持有
}
}
三、真实案例:银行转账死锁
让我们看一个经典的"银行转账"死锁案例:
// ❌ 错误实现:致命的死锁陷阱
public class BankAccount {
private String accountId;
private BigDecimal balance;
// 转账方法 - 存在死锁风险!
public static void transfer(BankAccount from, BankAccount to, BigDecimal amount) {
synchronized (from) { // 获取转出账户锁
synchronized (to) { // 获取转入账户锁
if (from.getBalance().compareTo(amount) >= 0) {
from.withdraw(amount);
to.deposit(amount);
System.out.println("转账成功: " + amount);
}
}
}
}
// 问题来了:如果同时发生两笔转账
// 转账1: A -> B
// 转账2: B -> A
// 线程1: 锁定A,等待B
// 线程2: 锁定B,等待A
// 死锁!
}
四、5分钟快速诊断:你的程序死锁了吗?
第一步:获取线程转储
# Linux/Mac
jstack>thread_dump.txt
# 或者使用jcmd
jcmdThread.print
# 如果在容器中
kubectl exec --jstack 1>dump.txt
第二步:查找死锁关键字
在线程转储中搜索这些关键词:
Found one Java-level deadlock: # 发现死锁!
"Thread-1":
waiting to lock monitor 0x00007f8a4c0c3b58 (object 0x00000000f6f8f9b8)
which is held by "Thread-2"
"Thread-2":
waiting to lock monitor 0x00007f8a4c0c3c58 (object 0x00000000f6f8f9a8)
which is held by "Thread-1"
第三步:使用JConsole/JVisualVM可视化分析
!example.com/deadlock-de… 图形化工具可以直观显示死锁关系
五、死锁解决"三板斧"
第一斧:避免锁顺序不一致(最常用)
// ✅ 正确实现:通过统一锁顺序避免死锁
public class BankAccountSafe {
private String accountId;
private BigDecimal balance;
public String getAccountId() {
return accountId;
}
// 安全的转账实现
public static void transferSafe(BankAccountSafe from,
BankAccountSafe to,
BigDecimal amount) {
// 关键:确定全局的锁顺序
BankAccountSafe firstLock = from;
BankAccountSafe secondLock = to;
// 通过账户ID确定锁定顺序
if (from.getAccountId().compareTo(to.getAccountId()) > 0) {
firstLock = to;
secondLock = from;
}
synchronized (firstLock) {
synchronized (secondLock) {
if (from.getBalance().compareTo(amount) >= 0) {
from.withdraw(amount);
to.deposit(amount);
System.out.println("安全转账: " + amount);
}
}
}
}
}
第二斧:使用tryLock超时机制
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockSolution {
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
public boolean tryTransfer(long timeout, TimeUnit unit)
throws InterruptedException {
long stopTime = System.nanoTime() + unit.toNanos(timeout);
while (true) {
// 尝试获取第一把锁
if (lock1.tryLock()) {
try {
// 尝试获取第二把锁
if (lock2.tryLock()) {
try {
// 执行业务逻辑
doBusiness();
return true;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock(); // 释放第一把锁
}
}
// 检查是否超时
if (System.nanoTime() > stopTime) {
return false; // 超时,返回失败
}
// 短暂休眠,避免CPU忙等
Thread.sleep(new Random().nextInt(10));
}
}
}
第三斧:使用资源层级锁
public class ResourceHierarchy {
// 定义全局资源层级
private static final int RESOURCE_A_LEVEL = 1;
private static final int RESOURCE_B_LEVEL = 2;
private static final int RESOURCE_C_LEVEL = 3;
public void accessResources(Object resourceA,
Object resourceB,
Object resourceC) {
// 按照资源层级顺序加锁
Object level1 = getResourceByLevel(RESOURCE_A_LEVEL, resourceA, resourceB, resourceC);
Object level2 = getResourceByLevel(RESOURCE_B_LEVEL, resourceA, resourceB, resourceC);
Object level3 = getResourceByLevel(RESOURCE_C_LEVEL, resourceA, resourceB, resourceC);
synchronized (level1) {
synchronized (level2) {
synchronized (level3) {
// 安全地访问所有资源
}
}
}
}
}
六、高级技巧:死锁检测与自动恢复
1. 使用守护线程监控
@Component
@Slf4j
public class DeadlockDetector {
private final ScheduledExecutorService scheduler =
Executors.newSingleThreadScheduledExecutor();
@PostConstruct
public void startDetection() {
// 每30秒检测一次死锁
scheduler.scheduleAtFixedRate(this::detectAndResolve,
0, 30, TimeUnit.SECONDS);
}
private void detectAndResolve() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
// 查找死锁线程
long[] deadlockedThreadIds = threadBean.findDeadlockedThreads();
if (deadlockedThreadIds != null && deadlockedThreadIds.length > 0) {
log.error("发现死锁!涉及线程数: {}", deadlockedThreadIds.length);
// 记录死锁详情
for (long threadId : deadlockedThreadIds) {
ThreadInfo threadInfo = threadBean.getThreadInfo(threadId);
log.error("死锁线程: {}, 状态: {}, 等待锁: {}, 持有锁: {}",
threadInfo.getThreadName(),
threadInfo.getThreadState(),
threadInfo.getLockName(),
threadInfo.getLockOwnerName());
}
// 尝试自动恢复(谨慎使用!)
autoRecover(deadlockedThreadIds);
}
}
private void autoRecover(long[] deadlockedThreadIds) {
// 策略1:中断其中一个死锁线程
ThreadInfo[] threadInfos = ManagementFactory.getThreadMXBean()
.getThreadInfo(deadlockedThreadIds, 0);
if (threadInfos.length > 0) {
// 找出所有线程,选择优先级最低的进行中断
Thread targetThread = findThreadById(threadInfos[0].getThreadId());
if (targetThread != null) {
log.warn("尝试中断线程以解除死锁: {}", targetThread.getName());
targetThread.interrupt();
// 发送告警通知
sendAlert("系统检测到死锁,已自动中断线程: " + targetThread.getName());
}
}
}
}
2. 使用断路器模式
@Slf4j
public class CircuitBreakerWithDeadlockProtection {
private final Lock lock = new ReentrantLock();
private final AtomicInteger failureCount = new AtomicInteger(0);
private volatile long lastFailureTime = 0;
private volatile State state = State.CLOSED;
enum State { CLOSED, OPEN, HALF_OPEN }
public <T> T execute(Supplier<T> supplier, T fallbackValue) {
if (state == State.OPEN) {
if (System.currentTimeMillis() - lastFailureTime > 5000) {
state = State.HALF_OPEN;
} else {
return fallbackValue; // 快速失败
}
}
if (!lock.tryLock()) {
// 如果无法快速获取锁,可能发生死锁风险
failureCount.incrementAndGet();
if (failureCount.get() > 10) {
state = State.OPEN; // 打开断路器
lastFailureTime = System.currentTimeMillis();
}
return fallbackValue;
}
try {
T result = supplier.get();
// 成功,重置计数器
failureCount.set(0);
state = State.CLOSED;
return result;
} finally {
lock.unlock();
}
}
}
七、生产环境死锁事故复盘
事故背景
某电商平台在"双11"大促期间,订单系统突然卡死,所有下单请求超时。
问题代码
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;
@Autowired
private CouponService couponService;
@Transactional
public Order createOrder(OrderRequest request) {
// 锁定库存
synchronized (inventoryService.getLock(request.getProductId())) {
// 锁定优惠券
synchronized (couponService.getLock(request.getUserId())) {
// 业务逻辑
}
}
}
public void cancelOrder(String orderId) {
// 先锁定优惠券
synchronized (couponService.getLock(userId)) {
// 再锁定库存
synchronized (inventoryService.getLock(productId)) {
// 业务逻辑
}
}
}
}
死锁发生场景
-
用户A下单:锁定库存A → 请求锁定优惠券A
-
同时用户A取消订单:锁定优惠券A → 请求锁定库存A
-
结果:创建订单线程和取消订单线程互相等待,死锁!
解决方案
// ✅ 修复方案:统一锁顺序
@Service
public class OrderServiceFixed {
// 定义全局锁顺序:先锁用户,再锁商品
public void processOrder(OrderRequest request, boolean isCreate) {
Object userLock = getUserLock(request.getUserId());
Object productLock = getProductLock(request.getProductId());
// 统一先锁用户,再锁商品
synchronized (userLock) {
synchronized (productLock) {
if (isCreate) {
createOrderInternal(request);
} else {
cancelOrderInternal(request);
}
}
}
}
// 使用数据库行锁替代synchronized
@Transactional
public Order createOrderWithRowLock(OrderRequest request) {
// 使用SELECT ... FOR UPDATE 锁定用户记录
User user = userRepository.findByIdForUpdate(request.getUserId());
// 使用SELECT ... FOR UPDATE 锁定商品记录
Product product = productRepository.findByIdForUpdate(request.getProductId());
// 业务逻辑...
}
}
八、预防死锁的"铁律"
铁律1:锁顺序一致性
// ❌ 错误:不同方法中锁顺序不一致
public void method1() { sync(A) { sync(B) {} } }
public void method2() { sync(B) { sync(A) {} } } // 危险!
// ✅ 正确:全局统一定义锁顺序
private static final Comparator<Object> LOCK_ORDER = (o1, o2) ->
System.identityHashCode(o1) - System.identityHashCode(o2);
public void safeMethod(Object a, Object b) {
Object first = a, second = b;
if (LOCK_ORDER.compare(a, b) > 0) {
first = b; second = a;
}
synchronized (first) {
synchronized (second) {
// ...
}
}
}
铁律2:锁超时机制
// 使用ReentrantLock的tryLock
private final Lock lock = new ReentrantLock();
public void doWithTimeout(long timeout, TimeUnit unit) {
try {
if (lock.tryLock(timeout, unit)) {
try {
// 业务逻辑
} finally {
lock.unlock();
}
} else {
// 超时处理:记录告警,降级处理
log.warn("获取锁超时,执行降级逻辑");
fallback();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
铁律3:减少锁粒度
// ❌ 错误:锁粒度过大
public synchronized void processOrder() { // 锁整个对象
// 几十行代码...
}
// ✅ 正确:细化锁粒度
private final Object orderLock = new Object();
private final Object userLock = new Object();
public void processOrder() {
// 只有需要同步的部分加锁
synchronized (orderLock) {
// 仅同步订单相关操作
}
// 其他非同步操作
updateLog();
}
铁律4:使用无锁数据结构
// 使用并发集合代替手动同步
private final ConcurrentHashMap<String, User> userCache =
new ConcurrentHashMap<>();
private final AtomicInteger counter = new AtomicInteger(0);
private final LongAdder totalAmount = new LongAdder();
// 使用CopyOnWriteArrayList避免读锁
private final CopyOnWriteArrayList<Listener> listeners =
new CopyOnWriteArrayList<>();
九、死锁检测工具推荐
-
VisualVM - 图形化死锁检测
-
JConsole - JDK自带监控工具
-
arthas - 阿里开源的Java诊断工具
-
jstack - 命令行线程转储分析
-
YourKit - 商业级性能分析工具
# 使用arthas检测死锁
$java-jararthas-boot.jar
$dashboard # 查看实时监控
$thread-b # 检测死锁
$thread--stateBLOCKED # 查看阻塞线程
十、总结:死锁防御体系
构建完善的死锁防御体系,需要从四个层面入手:
1. 编码规范层
-
制定团队锁使用规范
-
代码审查重点检查锁顺序
-
使用静态代码分析工具(如SonarQube)
2. 架构设计层
-
避免过度使用同步
-
采用消息队列解耦
-
使用乐观锁、无锁编程
3. 监控告警层
-
实时监控线程状态
-
死锁自动检测告警
-
定期生成线程分析报告
4. 应急预案层
-
制定死锁应急处理流程
-
准备熔断降级方案
-
建立自动恢复机制
记住:预防死锁比解决死锁更重要。良好的设计和规范,能让你的系统远离"鬼打墙"的困境。
最后的小测试:看看下面这段代码有没有死锁风险?如何修复?
public class TestDeadlock {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
synchronized (lock2) {
System.out.println("Thread1");
}
}
}).start();
new Thread(() -> {
synchronized (lock2) {
synchronized (lock1) {
System.out.println("Thread2");
}
}
}).start();
}
}
把你的答案写在评论区,我们一起学习!
#Java并发 #死锁 #多线程 #性能优化 #系统设计