当前位置: 首页 > news >正文

线程池竟能把CPU干到100%?两个JDK BUG踩坑实录

  • 线程池竟能把CPU干到100%?两个JDK BUG踩坑实录
    • 一、CPU 100%惨案:核心线程数为0引发的血案
      • 1. 为什么会CPU飙满?
      • 2. JDK是怎么修复的?
      • 3. 避坑建议
    • 二、定时任务“失踪”:Long.MAX_VALUE引发的时间溢出
      • 1. 溢出的秘密:负负得不了正
      • 2. 修复方式与避坑
    • 三、总结:JDK BUG不可怕,避坑有方法

线程池竟能把CPU干到100%?两个JDK BUG踩坑实录

做Java开发的都知道,ScheduledExecutorService是日常处理定时任务的常用工具,稳定又可靠。但我最近踩了两个它的隐藏BUG,其中一个直接把CPU飙到100%,另一个让定时任务彻底“罢工”。今天就把这两个坑扒出来,带你看看JDK底层的小猫腻。

一、CPU 100%惨案:核心线程数为0引发的血案

先看一段看似正常的代码,这段代码能直接跑起来,但会让CPU在任务执行前疯狂飙高:

publicstaticvoidmain(String[]args){// 核心线程数设置为0ScheduledExecutorServiceexecutor=Executors.newScheduledThreadPool(0);// 60秒后执行任务executor.schedule(()->{System.out.println("业务逻辑执行完毕");},60,TimeUnit.SECONDS);executor.shutdown();}

你没看错,核心线程数设为0的ScheduledThreadPoolExecutor居然能正常创建,而且会在60秒内把单个CPU核心吃到100%。这不是业务逻辑的问题,而是JDK的原生BUG。

1. 为什么会CPU飙满?

要搞懂这个问题,得钻进线程池的核心方法ThreadPoolExecutor.getTask()里看看。这个方法是线程池获取任务的关键,里面有个无限循环:

privateRunnablegetTask(){booleantimedout=false;for(;;){intc=ctl.get();intrs=runStateOf(c);// 状态判断逻辑省略...intwc=workerCountOf(c);// 判断线程是否需要超时回收booleantimed=allowCoreThreadTimeout||wc>corePoolSize;// 线程回收逻辑省略...try{// 超时获取任务或阻塞获取任务Runnabler=timed?workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS):workQueue.take();if(r!=null)returnr;timedout=true;}catch(InterruptedExceptionretry){timedout=false;}}}

当核心线程数corePoolSize=0时,会触发两个关键条件:

  • wc > corePoolSize成立(默认会创建1个工作线程,wc=1),所以timed=true
  • ScheduledThreadPoolExecutor的默认keepAliveTime是0纳秒。

这就导致线程会在workQueue.poll(0, NANOSECONDS)处无限循环——每次poll都立即返回null,然后进入下一次循环,全程不阻塞,相当于一个无意义的死循环,直接把CPU干满。

更坑的是,这个循环会一直持续到任务执行的那一刻,比如上面代码中就会持续60秒的CPU 100%占用。

2. JDK是怎么修复的?

这个BUG在JDK 9中被正式修复,修复方式特别简单:把默认keepAliveTime从0纳秒改成了10毫秒。

// JDK 9修复后privatestaticfinallongDEFAULT_KEEPALIVE_MILLIS=10L;publicScheduledThreadPoolExecutor(intcorePoolSize){super(corePoolSize,Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS,MILLISECONDS,newDelayedWorkQueue());}

10毫秒的超时时间,让线程在没任务时会阻塞10毫秒,而不是无限循环,CPU占用瞬间就降下来了。

3. 避坑建议

  • 不要把ScheduledThreadPoolExecutor的核心线程数设为0,哪怕是配置失误也不行;
  • 如果使用JDK 8及以下版本,创建线程池时手动指定keepAliveTime
    // JDK 8避坑写法ScheduledExecutorServiceexecutor=newScheduledThreadPoolExecutor(0,Executors.defaultThreadFactory(),newThreadPoolExecutor.AbortPolicy()){{setKeepAliveTime(10,TimeUnit.MILLISECONDS);}};

二、定时任务“失踪”:Long.MAX_VALUE引发的时间溢出

除了CPU飙满的BUG,scheduleWithFixedDelay还藏着一个溢出问题。看这段代码:

publicstaticvoidmain(String[]args)throwsInterruptedException{ScheduledExecutorServiceexecutor=Executors.newSingleThreadScheduledExecutor();// 延迟Long.MAX_VALUE微秒执行下一次任务executor.scheduleWithFixedDelay(()->{System.out.println("定时任务执行");},0,Long.MAX_VALUE,TimeUnit.MICROSECONDS);// 提交一个立即执行的任务executor.submit(()->{System.out.println("立即任务执行");});Thread.sleep(5000);executor.shutdownNow();}

运行后你会发现,只有“定时任务执行”输出,“立即任务执行”居然消失了!线程池直接陷入不可用状态。

1. 溢出的秘密:负负得不了正

scheduleWithFixedDelay的底层会把延迟时间存储在period字段中,而且为了区分“固定延迟”和“固定速率”,会把period设为负数:

publicScheduledFuture<?>scheduleWithFixedDelay(Runnablecommand,longinitialDelay,longdelay,TimeUnitunit){// 省略参数校验...ScheduledFutureTask<Void>sft=newScheduledFutureTask<>(command,null,triggerTime(initialDelay,unit),unit.toNanos(-delay));// 延迟时间取反,转为负数// 省略后续逻辑...}

delay=Long.MAX_VALUE且时间单位是微秒时,unit.toNanos(-delay)会发生溢出:

  • -Long.MAX_VALUE-9223372036854775807
  • 微秒转纳秒需要乘以1000,这个数值乘以1000后会超出long的范围,最终变成Long.MIN_VALUE-9223372036854775808)。

后续计算下一次执行时间时,会把period取反:-Long.MIN_VALUE依然是Long.MIN_VALUE(因为Long.MIN_VALUE的绝对值比Long.MAX_VALUE大1)。这就导致任务的下一次执行时间被算成了“292年前”,线程池一直等着这个过去的时间,后续提交的任务全被阻塞。

2. 修复方式与避坑

JDK 9同样修复了这个问题,把unit.toNanos(-delay)改成了-unit.toNanos(delay),先转纳秒再取反,避免溢出:

// JDK 9修复后unit.toNanos(-delay)-unit.toNanos(delay)

日常开发中避坑也简单:

  • 不要给scheduleWithFixedDelay设置Long.MAX_VALUE这么极端的延迟时间;
  • 如果确实需要超长延迟,改用TimeUnit.NANOSECONDS作为时间单位,避免乘法溢出。

三、总结:JDK BUG不可怕,避坑有方法

这两个BUG都暴露了JDK底层的细节漏洞,但只要掌握核心逻辑,就能轻松避开:

  1. 核心线程数不要乱设,ScheduledThreadPoolExecutorcorePoolSize建议至少设为1;
  2. JDK 8及以下版本使用ScheduledThreadPoolExecutor时,手动指定合理的keepAliveTime
  3. 避免使用极端数值作为定时任务的延迟时间,防止数值溢出;
  4. 条件允许的话,升级到JDK 9及以上版本,从根源上规避这些已修复的BUG。

线程池是Java并发的核心工具,看似简单的API背后可能藏着复杂的底层逻辑。遇到问题时多扒扒源码,不仅能解决当下的坑,还能加深对并发机制的理解,何乐而不为?

http://www.jsqmd.com/news/337207/

相关文章:

  • 从金鱼记忆到博学大脑:构建企业级AI Agent的完整指南(建议收藏)
  • 2026年威海性价比高的科技住宅楼盘,已建成的住宅楼盘排名 - 工业推荐榜
  • 机器学习 - 堆叠集成(Stacking)
  • 美国财务分析求职咨询哪家高效:财务咨询TOP10指南 - 技研备忘录
  • 简单理解:电力电子器件工作的开关频率越高,低次谐波越小,但开关器件的开关损耗也越大,带来散热问题同时也会使电能变换效率降低。高压大功率电力电子器件工作频率一般不高于1kHz
  • web页面上如何使用js实现大附件的切片上传?
  • CellHostMicrobe | 沈其荣院士团队揭示土壤原生动物捕食驱动细菌代谢从竞争向合作转变
  • 2026年 压力容器厂家推荐排行榜:专业制造与安全性能深度解析,助力工业高效稳定运行 - 品牌企业推荐师(官方)
  • 唤醒大脑潜能,实现高效学习
  • ZYNQ让卫星在太空“换脑”:基于动态部分可重构的星载智能处理革命
  • 网络工程毕业设计新颖的题目怎么选
  • 2026年热门的直流电源直流接触器/逆变器直流接触器厂家推荐及选择参考 - 品牌宣传支持者
  • 百度WebUploader组件如何实现内网大文件的分段上传?
  • 百度开源上传组件如何处理内网超大文件的续传?
  • Claude在AI原生应用中的5大核心优势解析
  • 数据来了,这个冬天油车门店人挤人,电车门店冷冷清清,竟然是真的!
  • 日光温室大棚供应商价格对比,温州山东冠创性价比高 - 工业设备
  • 第 175 场双周赛Q1——3823. 反转一个字符串里的字母后反转特殊字符
  • 医院电子病历系统用WordPress粘贴WORD内容,如何固定图片位置?
  • 2026年口碑好的军工设备高压直流继电器/叉车高压直流继电器最新TOP厂家排名 - 品牌宣传支持者
  • python+vue开发的电影院订票选座 票务员工信息管理系统三个角色-pycharm DJANGO FLASK
  • 2026年好用的垫片厂家直销,铁垫片性价比哪家高 - 工业品牌热点
  • 肝了!40 个 SpringBoot 常用注解!!
  • 科研党收藏!AI论文软件 千笔ai写作 VS speedai,专科生专属高效写作神器
  • 【无人机】基于matlab四旋翼无人机建模+分析+仿真(非线性刚体动力学模型、解耦的线性传递函数模型)【含Matlab源码 15048期】
  • 2026年适合老人的富硒猪肉肠品牌哪家口碑好 - mypinpai
  • 国防单位用WordPress粘贴机密WORD图片,有哪些加密传输方案?
  • RAG调优全攻略:解决大模型知识库检索难题,从入门到精通的实战指南
  • 图解AI三大核心技术:RAG、大模型、智能体
  • 从零开始学 RabbitMQ:小白也能懂的消息队列实战指南