必须为循环设置硬性退出兜底和阻塞等待机制:声明计数器并每次递增、开头检查超限退出;用blockingqueue.take()等阻塞替代轮询;volatile保障变量可见性;上线前配置max_execution_time、cpu告警及cgroups限制。

核心是让循环主动让出 CPU 时间片,而不是靠“逻辑上应该能退出”来赌运行时状态。漏掉迭代步长(比如 i++ 没执行、控制变量未更新、条件依赖的外部状态卡死)会让 while 变成事实上的 while(true),线程持续抢占单个 CPU 核心,温度飙升、响应迟滞、监控失敏——这不是性能问题,是硬件风险。
加硬性退出兜底:计数器必须存在且生效
别信“业务上最多跑 500 次”,生产环境要按最坏情况设上限:
- 在循环前声明整型计数器和最大允许次数,例如
DECLARE i INT DEFAULT 0; DECLARE max_iter INT DEFAULT 10000; - 每次循环体末尾强制自增:
SET i = i + 1;,不能放在IF分支里(避免被跳过) - 循环开头立即检查:
IF i >= max_iter THEN LEAVE my_loop; END IF; - 循环结束后查日志或打点:若
i == max_iter才退出,说明业务逻辑已失效,需立即告警并修复
杜绝空转等待:用阻塞代替轮询
当循环本意是“等某个条件就绪”,就别自己反复查——交给操作系统调度:
- Java 场景优先用
BlockingQueue.take()或CountDownLatch.await(),它们底层调用park(),不消耗 CPU - 避免手写
while (!flag) { Thread.sleep(1); }:sleep 是退让,但仍是轮询;正确做法是用wait/notify或Condition.await()实现唤醒机制 - 数据库侧(如 MySQL 存储过程)不用
SLEEP(0.01)降速,它只是把 100% 变成 99%,仍属无效计算
确保控制变量可变且可见
漏步长常源于变量没真正更新,或更新了别的线程看不见:
- Java 中控制循环的布尔变量必须加
volatile,否则 JIT 可能将其优化为常量缓存 - 避免仅靠本地变量判断,例如
boolean ready = check(); while (!ready) { ... }→ 应改为while (!check()) { ... }或用原子引用 - 存储过程中
SELECT INTO赋值失败会导致变量保持默认值,务必检查ROW_COUNT()并设 fallback
上线前必做两件事:超时+监控双保险
单靠代码逻辑兜底不可靠,必须叠加运行时防护:
- MySQL 调用前加会话级熔断:
SET SESSION max_execution_time = 30000;(30 秒),对存储过程内语句也生效 - Java 服务中给关键循环线程设置 CPU 时间阈值告警,例如用 Micrometer 上报
ThreadMXBean.getCurrentThreadCpuTime(),超 5 秒无进展即触发 dump - Linux 部署时启用
cgroups限制单进程 CPU 使用率,防止单核满载拖垮整机











