
下午2:30,我正在测试新开发的系统。运行初期一切正常,吞吐量达到预期。然而,在持续运行约15分钟后,系统突然完全停止响应。
首先我检查了系统资源:
使用jstack获取线程转储后,真相开始浮出水面:
"Thread-1" #12 prio=5 os_prio=0 tid=0x00007f8e3410b800 nid=0x6e1f waiting for monitor entry [0x00007f8e2a3f6000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.trading.Main.lambda$main$1(Main.java:25)
- waiting to lock <0x000000076abb6d40> (a java.lang.Object)
- locked <0x000000076abb6d50> (a java.lang.Object)
"Thread-0" #11 prio=5 os_prio=0 tid=0x00007f8e3410a000 nid=0x6e1e waiting for monitor entry [0x00007f8e2a4f7000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.trading.Main.lambda$main$0(Main.java:15)
- waiting to lock <0x000000076abb6d50> (a java.lang.Object)
- locked <0x000000076abb6d40> (a java.lang.Object)线程转储清晰地显示了一个典型的死锁场景:Thread-0持有锁A等待锁B,Thread-1持有锁B等待锁A。
回顾我编写的代码,问题出现在以下代码模块中:
public class AccountTransfer {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public void transfer(Account from, Account to, BigDecimal amount) {
// 模拟复杂的转账逻辑
if (from.getAccountId() < to.getAccountId()) {
synchronized (from) {
synchronized (to) {
executeTransfer(from, to, amount);
}
}
} else {
synchronized (to) {
synchronized (from) {
executeTransfer(from, to, amount);
}
}
}
}
}看似有顺序的加锁策略,实际上在高压环境下仍然出现了死锁。
为了更清晰地分析问题,我创建了一个简化版的死锁重现代码:
public class Main {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1 holding lock 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread 1 holding lock 2...");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2 holding lock 2...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread 2 holding lock 1...");
}
}
});
t1.start();
t2.start();
}
}经过深入分析,我认识到死锁的产生必须同时满足以下四个条件:
资源不能被共享,只能由一个线程独占使用。在我的代码中,synchronized关键字创建的锁就是互斥资源。
线程已经持有了至少一个资源,但又等待获取其他线程持有的资源。
资源只能由持有它的线程主动释放,不能被强制剥夺。
存在一个线程资源的循环等待链:T0等待T1持有的资源,T1等待T2持有的资源,...,Tn等待T0持有的资源。
让我详细重现死锁发生的具体时序:
时间点 Thread 1 Thread 2
-------------------------------------------------------
t0 获取lock1 获取lock2
t1 输出"持有lock1" 输出"持有lock2"
t2 睡眠100ms 睡眠100ms
t3 醒来,尝试获取lock2 醒来,尝试获取lock1
t4 等待lock2被释放 等待lock1被释放
t5 → 死锁形成 ←最基本的解决方案是确保所有线程以相同的顺序获取锁:
public class FixedLockOrder {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void transfer() {
// 使用统一的锁获取顺序
Object firstLock = System.identityHashCode(lock1) < System.identityHashCode(lock2) ? lock1 : lock2;
Object secondLock = firstLock == lock1 ? lock2 : lock1;
synchronized (firstLock) {
synchronized (secondLock) {
// 执行核心逻辑
System.out.println("转账操作执行成功");
}
}
}
}可以使用tryLock方法,我的代码如下:
import java.util.concurrent.locks.ReentrantLock;
public class TryLockSolution {
private static final ReentrantLock lock1 = new ReentrantLock();
private static final ReentrantLock lock2 = new ReentrantLock();
public static boolean transferWithTimeout() {
while (true) {
if (lock1.tryLock()) {
try {
if (lock2.tryLock()) {
try {
// 执行转账逻辑
System.out.println("转账成功");
return true;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
// 添加随机延迟避免活锁
try {
Thread.sleep(10 + (int)(Math.random() * 10));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
}更优雅的解决方案是基于资源的固有顺序进行加锁:
public class ResourceOrderingSolution {
public void transfer(Account from, Account to, BigDecimal amount) {
// 根据账户ID确定加锁顺序
Account first = from.getAccountId() < to.getAccountId() ? from : to;
Account second = first == from ? to : from;
synchronized (first) {
synchronized (second) {
if (from.getBalance().compareTo(amount) >= 0) {
from.debit(amount);
to.credit(amount);
System.out.println("转账成功: " + amount);
}
}
}
}
}死锁可能在系统运行很长时间后才出现,需要持续的监控和检测。熟练掌握jstack、jconsole等工具是诊断并发问题的关键。其次,在架构设计阶段就考虑并发安全问题,比后期修复成本低得多,良好的设计可以预防死锁发生。
这次死锁问题的解决过程,让我深刻认识到并发编程的复杂性和挑战性。从最初的困惑到最终的问题解决,整个过程就像侦探破案一样,需要耐心、细心和系统性的思考。
最大的收获不是解决了具体的技术问题,而是建立了一套完整的并发问题分析和解决框架。这种系统性的思维方式,将帮助我在未来面对更复杂的分布式系统问题时,能够更加从容和高效。
随着系统规模的不断扩大,单纯的单机并发控制已经无法满足需求。下一步我计划深入研究分布式锁、共识算法等领域,为构建更高可用性的分布式系统做好准备。
死锁问题只是并发编程冰山一角,但通过深入理解这个经典问题,我为后续的技术成长奠定了坚实的基础。每一次技术挑战都是成长的机会,记录这些经验不仅是为了避免重蹈覆辙,更是为了形成自己的技术方法论体系。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。