应按账户id升序加锁以避免死锁,使用synchronized(account)锁定final账户对象实现细粒度可重入锁,确保转账原子性与一致性。

在 Java 高并发转账场景中,synchronized 可以用,但**直接用它保护整个转账方法或用字符串锁是危险且低效的**。核心问题不是“怎么写 synchronized”,而是“如何正确控制锁粒度、避免死锁、保证一致性”。下面从关键原则和可落地的写法讲清楚。
锁定账户对象本身(推荐:细粒度 + 可重入)
转账本质是两个账户余额变动(A 减、B 增),必须保证原子性。最稳妥的方式是按固定顺序对两个账户对象加锁,避免死锁:
- 对账户 ID 排序(如小 ID 先锁),确保所有线程加锁顺序一致
- 使用
synchronized(account),前提是 Account 是 final 的、不被替换的对象引用 - Account 类内部字段(如 balance)应为 private,不暴露 setter
示例代码:
public void transfer(Account from, Account to, BigDecimal amount) {
// 确保加锁顺序:ID 小的先锁,打破循环等待
Account first = from.getId().compareTo(to.getId())
避免 String 或 Class 锁(常见坑)
以下写法看似简单,实则高危:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
synchronized("lock"):字符串常量池共享,全局串行,彻底丧失并发性 -
synchronized(Account.class):整个类级别锁,所有转账排队,吞吐归零 -
synchronized(this)(在 service 层):锁的是 service 实例,多个转账仍并发,无意义
这些都不是“锁账户”,而是锁无关对象,无法保证数据一致性。
配合数据库乐观锁(生产必备)
仅靠 JVM 层 synchronized 无法应对分布式部署、JVM 重启、缓存不一致等情况。真实系统必须叠加数据库层防护:
- 账户表增加
version字段,更新时校验:UPDATE account SET balance = ?, version = version + 1 WHERE id = ? AND version = ? - JDBC 执行后检查
executeUpdate()返回值是否为 1,否则重试(最多 3 次) - synchronized 用于单 JVM 内快速失败(减少 DB 冲突),DB 乐观锁兜底最终一致性
比 synchronized 更优的替代方案
当业务复杂或需更高性能时,考虑更现代的手段:
-
java.util.concurrent.locks.ReentrantLock:支持超时、可中断、公平性控制,便于诊断死锁 - 基于 Redis 的分布式锁(如 Redisson):跨 JVM 协调,适合微服务架构
- 使用 Seata 或 Saga 模式:处理跨服务转账,保证最终一致性
- 异步化 + 对账补偿:高频转账走消息队列,后台异步落库+定时对账
单纯依赖 synchronized 已不适合中大型金融级系统,它只是并发控制链条中的一环,不是银弹。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










