线程饥饿本质是某线程长期得不到CPU执行机会,主因包括高优先级线程抢占、非公平锁插队、无限等待无超时及线程池任务丢弃;需通过降优先级、用公平锁、设超时、调优拒绝策略等预防。

Java中发生线程饥饿,本质是某个(或某些)线程长期得不到CPU执行机会,不是它不活跃,而是调度机制或代码逻辑让它“永远排在后面”。这和死锁不同——饥饿线程没被阻塞,只是始终轮不到运行。
高优先级线程持续抢占CPU
Java支持线程优先级(1–10),但JVM不保证严格遵循。不过在某些操作系统(如Windows)或特定JVM实现中,高优先级线程可能频繁被调度,导致低优先级线程长时间无法获得时间片。尤其当高优先级线程处于计算密集型循环、且未主动让出(如不调用Thread.yield()、sleep()或等待锁)时,低优先级线程就容易饿死。
- 避免显式设置过高优先级(如MAX_PRIORITY),除非有强实时需求
- 不要依赖优先级做业务逻辑的“公平性”保障——它不可靠、不可移植
不公平锁导致等待线程被反复跳过
ReentrantLock默认构造为非公平锁:新线程尝试加锁时,可直接与等待队列中的线程“抢”,而等待队列里的老线程可能连续多次被插队。若竞争激烈、新请求不断涌入,队首线程可能迟迟无法获取锁,形成饥饿。
- 必要时使用new ReentrantLock(true)启用公平模式(牺牲吞吐换公平)
- 注意公平锁会显著降低并发性能,仅在确实观察到某线程长期卡在WAITING状态时再考虑
无限等待无超时的同步操作
调用Object.wait()、BlockingQueue.take()、CountDownLatch.await()等方法时,若没有设置超时,又缺乏可靠的唤醒机制(如漏写notify()、信号丢失、条件判断错误),线程就会永久挂起——表面是“等待”,实则是饥饿的一种表现(永远等不来唤醒)。
立即学习“Java免费学习笔记(深入)”;
- 优先选用带超时的版本(如wait(timeout)、poll(timeout))
- 确保每个wait()都有对应且正确的notify()/notifyAll()路径,尤其注意异常分支是否遗漏唤醒
线程池中任务持续堆积且拒绝策略不当
当线程池核心线程全忙、队列已满、新任务不断提交,又配置了DiscardPolicy或CallerRunsPolicy时,部分任务可能被静默丢弃,或由提交线程同步执行(拖慢调用方)。如果业务上某些关键任务总被晚提交、总进队尾、又总被丢弃,也会表现出“饥饿感”——它的逻辑从没被执行。
- 监控线程池的getQueue().size()和getActiveCount(),及时发现积压
- 根据场景选合适拒绝策略;必要时自定义RejectedExecutionHandler,记录日志或降级处理,而非简单丢弃
基本上就这些。饥饿不是语法错误,而是并发设计中对资源分配、调度语义和唤醒契约的疏忽所致。它难复现、难定位,但往往暴露在线上长周期运行后——多关注线程状态分布、锁竞争热点和任务延迟指标,比单纯看CPU更有效。










