避免死锁的关键是统一加锁顺序:始终按账户ID升序先锁小ID后锁大ID,推荐用long型ID比较,字符串则用compareTo,禁用identityHashCode;加锁逻辑须原子执行,封装为lockAccounts工具方法;建议使用ReentrantLock配合超时与中断处理,并辅以减少锁粒度、分段锁或CAS乐观锁优化。

在 Java 多线程转账业务中,避免死锁的关键是**统一加锁顺序**:对涉及的两个账户按唯一、稳定、全局一致的规则排序,始终先锁 ID 小的账户,再锁 ID 大的账户。
用账户 ID 做自然排序依据
账户通常有唯一标识(如 long id 或 String accountId),可直接用于比较。只要所有线程对同一对账户都遵循“小 ID 先加锁、大 ID 后加锁”,就不会出现 A 线程持 A 锁等 B、B 线程持 B 锁等 A 的循环等待。
- 推荐使用 long 类型 ID,比较高效且无歧义(
a.id ) - 若用字符串 ID,用
accountId.compareTo(otherId) 保证字典序一致 - 避免用内存地址(如
System.identityHashCode())——虽能排序,但不保证跨 JVM 或重启后稳定,可能引发偶发死锁
加锁逻辑必须原子且不可绕过
不能靠“先 tryLock 再判断顺序”这种松散方式。必须在获取锁前就确定好顺序,并严格按序执行 lock 操作。
- 典型错误:先对 accountA.lock(),失败再换 accountB —— 这破坏了顺序一致性
- 正确做法:先比较 ID,明确谁小谁大,再依次调用
smallAccount.lock()→largeAccount.lock() - 建议封装成工具方法,如
lockAccounts(Account a, Account b),内部完成排序和加锁,业务代码只调用一次
使用可重入锁 + 中断响应更健壮
推荐用 ReentrantLock 替代 synchronized,便于超时控制与中断处理:
立即学习“Java免费学习笔记(深入)”;
- 用
lock.tryLock(timeout, TimeUnit)避免无限等待,超时后释放已获锁并重试或报错 - 加锁过程需放在
try-finally中,确保任一锁获取失败时,已持有的锁能及时 unlock - 若线程被中断(
Thread.interrupted()),应主动退出并释放资源,防止锁泄漏
额外建议:减少锁粒度 + 引入无锁设计
加锁顺序只是基础保障,真正高并发下还需优化整体设计:
- 避免长事务:转账逻辑尽量轻量,不嵌入耗时 IO 或复杂计算
- 考虑分段锁或账户本地余额+异步对账,例如为每个账户配独立锁,转账时只锁两个相关账户(仍需保序)
- 极端场景可用 CAS + 乐观锁(如版本号字段),失败时重试,避开显式锁竞争


















