Java应用中LinkedBlockingQueue调用take()后无法继续执行问题排查
问题场景
Java应用使用LinkedBlockingQueue实现生产者-消费者模型,初期少量元素能正常消费,但运行一段时间后,pendingAlarmQueue.take()会卡住,即使向队列添加新元素也无法继续执行。本地Eclipse运行正常,但在客户服务器环境中频繁出现,重启服务器后仅能处理少量对象就再次冻结,日志无明显错误。
代码分析与问题排查
1. 消费者线程意外终止(最可能原因)
你的DelegatorThread.run()方法仅捕获了4种特定异常,但RuntimeException(如NullPointerException、IllegalArgumentException)和Error未被覆盖。如果al.getSeverity()或LOGGER.log()抛出这类未被捕获的异常,消费者线程会直接终止,不再循环执行take()。此时队列虽有新元素,但无线程消费,表现为take()卡住。
修复方式:扩展异常捕获范围,记录完整异常堆栈,避免线程静默死亡:
public void run() { try { while (true) { try { Alarm al = pendingAlarmQueue.take(); LOGGER.log(Level.INFO, al.getSeverity()); } catch (InterruptedException | RemoteException | EInternalException | EsymacException e) { LOGGER.log(Level.SEVERE, "消费异常", e); // 打印完整堆栈,不止是消息 } } } catch (Throwable t) { LOGGER.log(Level.SEVERE, "消费者线程意外终止", t); // 捕获所有致命异常 } }
2. 日志输出阻塞
若服务器日志系统出现故障(如磁盘满、日志框架死锁、远程日志服务无响应),LOGGER.log()会长期阻塞,导致线程卡在日志打印步骤,无法回到take()继续消费。此时队列元素积压,表现为take()卡住。
验证方式:临时注释日志打印语句,或切换为异步日志框架(如Logback异步Appender),观察问题是否复现。
3. InterruptedException处理不规范
线程被中断时,take()会抛出InterruptedException,当前代码仅打印日志但未恢复中断状态。虽然while(true)会继续循环,但后续take()无法响应中断信号,极端场景下可能引发异常行为。
修复方式:捕获中断异常后恢复状态,按需退出循环:
catch (InterruptedException e) { LOGGER.log(Level.SEVERE, "消费者线程被中断", e); Thread.currentThread().interrupt(); // 恢复中断状态 break; // 或根据业务逻辑决定是否退出循环 }
4. 类加载导致多实例(低概率)
若JsonToAlarmConverter被不同类加载器加载,会创建多个DelegatorThread实例,但因队列是static的,消费逻辑仍共享同一队列,一般不会导致卡死,但可排查服务器类加载配置确认。
关键修复建议
- 优先覆盖所有异常类型,确保线程终止时能留下日志线索。
- 检查服务器日志系统状态,避免日志输出阻塞业务线程。
- 规范处理中断异常,保证线程状态正确性。
- 确认
addAlarminQueue方法确实调用了delegatorThread.addJob(alarms),确保元素被正确入队。
内容的提问来源于stack exchange,提问作者Visweswararao Atluri

