审查 Java 转账代码,识别并发与原子性问题。

考查重点:条件扣款、事务与重复转账。检查余额条件扣减与入账是否同事务,重复请求是否幂等,多账户锁顺序是否一致;用并发交错和失败回滚验证余额与流水是否一致

本题目录

参考答案

核心回答

先看代码里余额检查与扣款是否构成可靠的并发控制,扣款与入账是否同成同败,以及重复请求会不会再次转账。没有给出具体代码时,只能说明审查路径,不能断言某一行一定有漏洞。

并发:读到余额足够,不等于扣款时仍足够

以下是独立的数值推演,不是原题代码:余额 100,两个请求各转出 80。若两者都先读到 100、分别算出剩余 20,再各自把余额写成 20,同时给两个收款方各加 80,就发生了扣款覆盖:最终付款方是 20,收款方却一共增加 160。

一种处理是由数据库执行带余额条件的扣减,并检查受影响行数:

UPDATE account
SET balance = balance - :amount
WHERE id = :from_id AND balance >= :amount;

这里还需要先校验金额为正、账户和权限合法,并保证金额表示准确;受影响行数不是 1 时,不能继续入账。也可以在事务内锁定相关账户后检查和更新,涉及多个账户时保持一致的加锁顺序,降低死锁风险。单个 Java 进程里的 synchronized 不能自动保护其他服务实例。

原子性:不能只扣款成功

在同一事务资源内,扣款、收款方入账、转账记录与幂等记录应按业务设计一起提交或回滚。审查 Spring 事务时,核对是否真的经过事务代理、所用事务管理器和回滚规则是否覆盖失败;只看到 @Transactional 不足以证明生效。默认代理模式下的同类内部调用尤其需要检查。

若两个账户跨库,或还要调用外部支付服务,本地事务不能自动包含全部动作,需要另行设计跨系统一致性与重试流程。

重复请求与验证

用稳定的业务请求标识和唯一约束识别重复转账,并检查相同标识是否对应相同参数;不能每次重试都换一个新标识。已完成请求返回已有结果,不能再扣一次。

测试应覆盖两个并发扣款、相反方向转账、重复提交、扣款后入账前失败及响应丢失后重试。检查余额与流水等业务不变量,并在实际数据库与事务配置下验证锁和回滚。上面的条件扣款只解决一部分并发检查,不能单独当作完整转账实现。

参考资料

相关面经

1 篇

字节跳动

后端开发工程师
一面
查看原面经 →
审查 Java 转账代码,有什么并发与原子性问题?

相关题目

4 题