long乘法溢出静默绕回致负数,常见于ID生成、计费等场景;线索包括数据库bigint突变负值、时间戳排序错乱、ArithmeticException告警骤降、指标跳变;高危写法如字面量缺L后缀。
long 类型乘法溢出不会报错,结果直接“绕回”,比如 long.max_value * 2 得到一个巨大负数。这种静默溢出在高频累加、id生成、时间差计算、计费统计等场景中极易埋雷,且上线后难以复现和定位。
看日志与监控中的异常信号
不依赖代码审查,先从运行态找线索:
- 数据库 bigint 字段写入值突变为负数(如订单金额、累计PV),尤其集中在某批任务或某类用户行为后
- 排序逻辑错乱:比如按时间戳升序查出的结果里,后面一条记录的时间戳反而比前面小几十亿
- 告警中出现
ArithmeticException的频率骤降——反而是危险信号,说明本该触发的Math.multiplyExact被注释或绕过了 - 监控图表中某指标(如单日总耗时纳秒、分片步长)突然归零或跳变,但无服务重启记录
查代码中三类高危乘法模式
重点扫描以下写法,它们几乎都隐含 int→long 的中间截断或符号误判:
-
字面量未带 L 后缀:如
1024 * 1024 * 1024 * 2,全程按 int 算,溢出后再转 long;应写成1024L * 1024 * 1024 * 2或1L -
DTO/接口参数类型不一致:RPC 接口定义为
long,但消费者传的是int,序列化框架(如 Hessian、Kryo)配置不当,自动截断高位,日志里看不出异常 -
混合符号乘法未预检:如
a * b,其中a = -1000000000000L,b = -1000000000000L,结果正溢出,但a * b > 0判断失效,需用Math.multiplyExact或手动分情况检查
用工具链主动暴露问题
别等线上崩了再查,把检测左移到开发和测试阶段:
- 编译期:启用
-Xlint:all,JDK 会警告类似integer number too large的字面量风险 - 单元测试:对所有乘法逻辑补边界用例,例如输入
Long.MAX_VALUE, 2, -1, 0, Long.MIN_VALUE,验证是否抛出预期异常或返回合理降级值 - 静态扫描:用 SonarQube 或 Alibaba Java Coding Guidelines 插件,规则
java:S2184(危险的整数运算)可识别未防护的long * long - 运行时防护:在核心路径(如计费引擎)统一封装
SafeLongMath.multiply(a, b),内部调用Math.multiplyExact,捕获ArithmeticException并打标 + 告警 + 切 BigInteger 降级
数据库与序列化层必须对齐精度
很多溢出其实在落地前就已发生,只是被掩盖:
- MyBatis 映射中,
<result column="amount" property="amount" javaType="int"/>→ 即使数据库是BIGINT,Java 端也早被截成int,后续任何乘法都基于错误值 - JSON 序列化(如 Jackson)默认把 long 当数字处理,若前端 JS 用 Number 接收(最大安全整数
2^53-1),超限后精度丢失,后端再乘一次就彻底失真 - 解决方案:所有涉及数值传输的环节,显式声明类型——DTO 字段用
Long包装类而非long;数据库字段映射强制指定javaType="java.lang.Long";JSON 接口对大数字段加@JsonFormat(shape = JsonFormat.Shape.STRING)转字符串传输

















