活锁和饥饿是死锁之外的两类活跃性问题:活锁表现为线程持续重试却总失败,消耗CPU;饥饿则是低优先级线程长期无法获得资源,处于WAITING状态。

活锁和饥饿是多线程设计中容易被忽视、但实际影响系统稳定性和公平性的活跃性问题。它们不像死锁那样明显阻塞线程,却可能导致 CPU 空转或低优先级任务永久得不到执行。避免的关键在于打破“重复失败循环”和“资源分配偏斜”,而不是单纯依赖锁机制本身。
避免活锁:打破同步重试的确定性
活锁的本质是线程在检测到冲突后主动让步并重试,但所有线程采用相同策略(如同时退避、统一等待时长),导致重试行为持续同步、反复碰撞。
-
引入随机退避时间:每次重试前,让线程等待一个随机毫秒数(如
Thread.sleep(new Random().nextInt(100))),打乱重试节奏,显著降低持续冲突概率。 - 错开重试时机或顺序:例如按线程 ID 取模决定初始延迟,或使用指数退避(第一次等 1ms,第二次等 2ms,第三次等 4ms……)并叠加随机扰动。
-
限制重试次数并设置兜底策略:超过阈值(如 5 次)后不再重试,而是抛出异常、记录告警,或转为阻塞式获取(如用
tryLock(timeout, unit)替代无限重试)。
避免饥饿:保障资源获取的公平性与响应性
饥饿常源于调度不公平(如高优先级线程长期霸占 CPU 或锁)、锁实现非公平(如默认 ReentrantLock 非公平模式),或低优先级线程因资源依赖链被间接压制。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
-
显式使用公平锁:创建
ReentrantLock时传入true参数(new ReentrantLock(true)),使其按 FIFO 顺序唤醒等待线程,避免新线程不断插队。 -
避免无节制的优先级依赖:不依赖线程优先级(
setPriority)做关键资源调度,因为 JVM 不保证跨平台的优先级语义;若必须分级,配合超时机制(如lock.tryLock(3, TimeUnit.SECONDS))防止低优线程无限等待。 -
定期提升等待线程权重:对长时间处于
WAITING状态的线程(如通过监控其等待锁时长),可在业务层触发“饥饿检测”,临时调整其调度权重或切换至专用公平队列处理。
设计层面的通用预防原则
很多活锁和饥饿问题其实在架构初期就能规避,不需要等到运行时才发现。
立即学习“Java免费学习笔记(深入)”;
- 减少锁粒度与持有时间:锁只包裹真正需要同步的代码段,避免在锁内执行 I/O、远程调用或长耗时计算,缩短其他线程等待窗口。
- 统一资源申请顺序:即使不为防死锁,也建议对多个锁/资源定义全局编号,所有线程按升序申请,降低因竞争模式差异引发的活锁风险。
-
用无锁结构替代争抢场景:对计数器、队列等高频操作,优先考虑
AtomicInteger、ConcurrentLinkedQueue或StampedLock的乐观读,从源头减少锁争用。

















