写在前面

这个话题来源于线上环境的一次真实问题定位,现象是分析dump文件发现线程池大多数线程都处于TIMED_WAITING或者是WAITING状态,其实这也不是什么大问题,线程数也不算太多,任务队列也没有堆积,本着对技术的学习和优化态度开始了研究之路

什么是TIMED_WAITING和WAITING状态

先列一下线程的几种状态

  1. 初始(NEW):新创建线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java 虚拟机中线程的可运行状态包括操作系统中线程的就绪(ready)和运行中(running)两种状态。即处于该状态时,线程可能正在等待来自操作系统的其他资源(如CPU)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

下面看下线程的状态变化图

很明显现在状态转换为TIMED_WAITING有5中方法,但是常见的还有Object.wait(long)和LockSupport.parkNaons(),WAITING状态也类似。

问题分析

分析1

经过堆栈分析,发现本次线上问题主要是因为线程池任务队列为空,各个线程一直处于等待任务的过程。但是通过监控发现活跃线程数还不及核心线程数的一半,但是存活线程数已经达到了最大线程数。举个例子,现在核心是32, 最大是200,但是现在活跃的,也就是正在跑任务的线程个数也就是20的样子,但是活跃的+等待的线程数却是打满到了200。

分析2

这个时候我就产生了疑问,活跃的也就20,还没到核心线程数,任务也没堆积,为什么空闲线程没有回收呢。带着这个疑问我进行了如下操作:

  1. 本地复现

首先确认是不是代码设置问题,排除了这一层之后,我在本地起了服务,然后起了1000个线程压测,用visualvm监测,发现线程数先是瞬间打满到200,然后慢慢的减少,最后稳定在32,也就是说空闲线程被回收了。

  1. 分析怀疑的点

确定代码层面没有问题之后,开始第二层分析。因为线上环境和本地环境最大的问题就是线上环境流量较大而且不间断,我就怀疑是线上环境下,线程池空闲线程没有等到超时就被新的任务唤醒去执行任务了,然后任务执行完之后发现队列没有任务了(被其他线程申请走了),就重新开始等待,所以线程几乎一直处于限时等待状态。

  1. 资料查询

在google上重点查询了,线程池线程回收机制,最后发现了这篇

https://blog.csdn.net/zhujiangtaotaise/article/details/122358731

这里面主要讲的是线程池分配任务给线程是按轮询机制的,底层是基于AQS中的等待队列

  1. 源码分析
private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}int wc = workerCountOf(c);// Are workers subject to culling?boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)return r;timedOut = true;} catch (InterruptedException retry) {timedOut = false;}}}

重点看workQueue.poll()这个方法,当设置allowCoreThreadTimeOut为true或者工作线程数大于核心线程数就会进入到这里。

public E poll(long timeout, TimeUnit unit) throws InterruptedException {E x = null;int c = -1;long nanos = unit.toNanos(timeout);final AtomicInteger count = this.count;final ReentrantLock takeLock = this.takeLock;takeLock.lockInterruptibly();try {while (count.get() == 0) {if (nanos <= 0L)return null;// 发现队列为空,通过 ReentrantLock 的 Condition 来实现阻塞等待线程存活时间nanos = notEmpty.awaitNanos(nanos);}x = dequeue();c = count.getAndDecrement();if (c > 1)notEmpty.signal();} finally {takeLock.unlock();}if (c == capacity)signalNotFull();return x;
}

这里队列使用的是LinkedBlockingQueue,实际上到这也差不多明晰了,后面都是 AQS队列的内容了,具体的可以查阅AQS相关,总之就是线程成功申请到任务执行之后就将线程放到队尾,相当于重新开始计时,所以一直处于等待状态

其他

  1. 线程池也有很多其他需要注意的点,不同的场景有不同的用法,比如对性能要求很高,需要快速响应,就需要将队列设置为0,或者修改策略,先增加线程数到最大线程数,再放入队列,保证任务快速执行。

  2. 线程池也提供了预热接口 prestartAllCoreThreads,预热单一或者所有核心线程,可以减少一开始流量过来线程创建的损耗。

小结

其实线程池最复杂的还是参数的设置,包括核心,最大,队列大小,拒绝策略,空闲时间,甚至采用何种队列。也有很多看似精细的算法来计算线程数,但是实际上很依靠开发者的工程经验。我们项目中大多还是采用线程池监控+动态线程池来达到最佳使用的目的!

线程池存在大量TIMED_WAITING状态线程相关推荐

  1. 线程及线程池的五种状态

    线程的5种状态 线程可以有如下5种状态:New .Runnable .Running .Blocked .Dead 状态之间的转换如图: 1.New (新创建) 当用new操作符创建一个线程时,如ne ...

  2. 线程池的五种状态详解

    线程池的5种状态:Running.ShutDown.Stop.Tidying.Terminated. 概述 最好的学习方式就是看源码,众所周知,创建线程池肯定会使用到ThreadPoolExecuto ...

  3. 线程池的五种状态及创建线程池的几种方式

    上篇<Java线程的6种状态详解及创建线程的4种方式> 前言:我们都知道,线程是稀有资源,系统频繁创建会很大程度上影响服务器的使用效率,如果不加以限制,很容易就会把服务器资源耗尽.所以,我 ...

  4. ThreadPoolExecutor的参数与线程池的五个状态

    ThreadPoolExecutor,它是Executors.newXxxxx()的返回结果,像Executors.newCachedThreadPool();,它实际上是这个: public sta ...

  5. Java线程池--线程池的五种状态

    线程池的5种状态:Running.ShutDown.Stop.Tidying.Terminated. 线程池各个状态切换框架图: 1.RUNNING (1) 状态说明:线程池处在RUNNING状态时, ...

  6. Java并发编程一线程池的五种状态

    推荐:Java并发编程汇总 Java并发编程一线程池的五种状态 原文地址 Java多线程线程池(4)–线程池的五种状态 正文 线程池的5种状态:Running.ShutDown.Stop.Tidyin ...

  7. 【Java 并发编程】线程池机制 ( 线程池执行任务细节分析 | 线程池执行 execute 源码分析 | 先创建核心线程 | 再放入阻塞队列 | 最后创建非核心线程 )

    文章目录 一.线程池执行任务细节分析 二.线程池执行 execute 源码分析 一.线程池执行任务细节分析 线程池执行细节分析 : 核心线程数 101010 , 最大小成熟 202020 , 非核心线 ...

  8. JAVA线程池原理以及几种线程池类型介绍

    在什么情况下使用线程池? 1.单个任务处理的时间比较短      2.将需处理的任务的数量大 使用线程池的好处: 1.减少在创建和销毁线程上所花的时间以及系统资源的开销      2.如不使用线程池, ...

  9. 浅谈线程池(中):独立线程池的作用及IO线程池

    在上一篇文章中,我们简单讨论了线程池的作用,以及CLR线程池的一些特性.不过关于线程池的基本概念还没有结束,这次我们再来补充一些必要的信息,有助于我们在程序中选择合适的使用方式. 独立线程池 上次我们 ...

最新文章

  1. oracle删除无效归档日志,求助:rman无法按照策略删除过期的归档日志
  2. swiper轮播插件的使用
  3. c语言中闰年 日期 天数 统计出在某个特定的年份中,出现了多少次既是13号又是星期五的情形
  4. python 装饰器 继承_Python设计模式之装饰器模式
  5. jenkins 忘记用户名和密码
  6. python安装环境配置
  7. Linux实现ICMP PING代码
  8. 简单的通用TreeView(WPF)
  9. 输入输出(I/O)流。
  10. Postman工具(环境变量与全局变量)
  11. 阿里云云计算 43 CDN的使用
  12. python 节气_Python开源日志01:pyGregorian2LunarCalendar公历农历转换、阳历阴历转换、二十四节气计算...
  13. 【NYNU 1151】轻羽飞扬 数塔DP
  14. Android开机速度优化 Android 开机时间优化
  15. python运行按钮灰色_点击后,tkinter菜单按钮变灰了
  16. Codeforces Round #548 (Div. 2) 1139 D+2021天梯赛l3-3 解题报告(负二项式分布+莫比乌斯容斥+杜教筛(天梯赛))
  17. aardio怎么运行php,aardio
  18. C4D演绎中国风设计这波电商BANNER背景素材,高级了
  19. 163yum源的配置安装
  20. ctfshow萌新计划web22

热门文章

  1. matplotlib绘图3——图标辅助元素的定制
  2. 这样的电子发票报销管理对会计来说是添堵还是助力?
  3. HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代 理,HAProxy支持数以万计并发连接
  4. Scanvenger游戏制作笔记(八)Unity3D关卡胜利条件的判断
  5. 《Linux/UNIX OpenLDAP实战指南》——1.3 OpenLDAP schema概念
  6. PHP处理kafka消息队列
  7. PySimpleGUI库的查询小程序开发
  8. 9.9能买什么?80节北美外教AI课+158张单词卡+6张卡通贴纸+1张字母表+Leo单肩小书包........
  9. 财务内控测试软件,了解内控和控制测试的区别
  10. html+css布局实例:制作的淘宝小图标的显示