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

Java线程池原理深度解析

一、七大参数

Java自定义线程池有七大参数,分别为核心线程数、最大线程数、阻塞队列、存活时间、时间单位、线程工厂和拒绝策略。为方便理解,下面将通过举例分析自定义线程池的工作流程。

二、举例分析

假定自定义了一个线程池,参数如下:

  • 核心线程数:6
  • 最大线程数:10
  • 阻塞队列:5
  • 存活时间:30
  • 时间单位:s
  • 线程工厂:默认
  • 拒绝策略:默认

一开始,线程池中是一个线程都没有的。此时,请求1过来了,请求1来到线程池后,首先会判断池中线程数是否达到核心线程数,很显然,没有达到,所以线程池就会创建出线程1,然后请求1不用入队直接被线程1处理。紧接着,请求2也来了,请求2来到线程池后,也是先判断池中线程数是否达到核心线程数,没有达到,则创建线程2,然后请求2不用入队直接被线程2处理。请求3请求4请求5和请求6都是同理,经过以上流程后,线程池分别创建出线程3线程4、线程5线程6来处理这三个请求。

请求7的时候,情况就有所不同了,请求7来到线程池,同样是判断池中线程数是否达到核心线程数,经过判断,发现池中线程数已经达到核心线程数了,此时就不用创建新线程了,而是去入队。当然入队之前还有一个操作,需要先判断阻塞队列有没有满,显而易见,阻塞队列并没有满,所以请求7直接入队。这里要提一点,假定这些请求都极其耗时,所以先创建的6个线程并没有空闲来队列里获取任务。请求8也是先判断池中线程数是否达到核心线程数,发现池中线程数已经达到核心线程数,于是就去判断阻塞队列有没有满,没满,则入队。请求9请求10请求11也是同理,那么此时线程池中的情况就为:先去6个线程正在处理最开始的6个请求,而后面来的5个请求则是进入到阻塞队列中等待,可以看出阻塞队列已经满了。

请求12来了会怎么样呢?请求12来了之后,同样的操作,判断池中线程数是否达到核心线程数,达到了,就尝试入队,结果发现阻塞队列已经满了,无法入队。既然无法入队,那就执行其他操作。此时请求12会判断池中线程数是否达到最大线程数,可以发现池中线程数并没有达到最大线程数,所以,线程池会创建线程7,紧接着,线程7略过阻塞队列中正在等待的任务,直接处理请求12,这里请求12的插队行为,让人不由地想到非公平锁,可以感受到线程池是带有非公平思想在里面的。接下来的请求13、请求14请求15同理,都是判断池中线程数是否达到核心线程数,达到了,就尝试入队,发现阻塞队列已经满了,入队失败就判断池中线程数是否达到最大线程数,没达到,则创建线程,并略过阻塞队列中正在等待的任务直接处理请求13、请求14请求15。到这时,线程池中的情景就变为了线程1~6分别处理请求1~6线程7~10分别处理请求12~15请求7~11在阻塞队列中等待。

此时若是再来个请求16,线程池会如何应对呢?前面的操作都是一样的,判断池中线程数是否达到核心线程数,达到了,则尝试入队,发现阻塞队列已满,则判断池中线程数是否达到最大线程数,达到了,则触发拒绝策略,而默认拒接策略是丢弃任务并抛异常,所以请求16被丢弃,同时线程池抛个异常出来。

现在,我们假设线程1~10陆续处理完手头上的任务,那么这些线程就会到阻塞队列中获取新的任务,获取到后进行处理,处理完后再次获取,循环往复。等到所有任务都处理完,阻塞队列为空时,线程1~10再去阻塞队列获取请求,就会在阻塞队列处,被阻塞队列给阻塞。这时,若是在存活时间(30s)内都没有新的请求过来,线程池就会用CAS随机销毁掉四个多余的线程,使池中线程数刚好为核心线程数;反之,若是在存活时间(30s)内有新的请求过来,线程池就不会销毁多余的四个线程。为什么是四个线程呢?自然是因为池中线程数比核心线程数多了四个。

三、源码分析

1、问题提出及源码

其实有个问题需要了解,那就是正常来讲线程执行完逻辑后就会消失,那么,线程池是如何做到保留核心线程数的呢?从上面可知,线程池是通过阻塞队列来保留核心线程数,所以直接上源码,看看它究竟怎么保留核心线程数。

    final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {while (task != null || (task = getTask()) != null) {w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted.  This// requires a recheck in second case to deal with// shutdownNow race while clearing interruptif ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);try {task.run();afterExecute(task, null);} catch (Throwable ex) {afterExecute(task, ex);throw ex;}} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}}

上面runWorker方法中,最需要关注的是while循环条件里的getTask方法,这是核心,下方有展示getTask方法的代码。

    private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?for (;;) {int c = ctl.get();// Check if queue empty only if necessary.if (runStateAtLeast(c, SHUTDOWN)&& (runStateAtLeast(c, 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;}}}

2、分析

为方便理解,我会把前面的例子和这里的源码结合起来分析。

看方法名就知道,这是获取任务时执行的代码,在getTask中有个死循环,进入其中后,通过int wc = workerCountOf(c);获取并记录池中线程数为10,boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;这里allowCoreThreadTimeOut默认为false,corePoolSize指的是核心线程数8,10 > 8,所以timed为true。

接下来有个if语句,其中maximumPoolSize代表最大线程数10,所以wc > maximumPoolSize为false,后面的timed为true,timedOut代表是否超时,在getTask方法刚开始,还没进入循环时就被赋值为false,所以timed && timedOut为false,所以(wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())为false,进不了if。

然后就到了try块中了,timed为true,所以执行workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),这个方法会去阻塞队列获取任务,若是在规定时间内获取到任务,则r != null为true,直接return r;若是没有获取到任务,则将timedOut设为true,然后接着循环。

假设都没获取到任务,那么这10个线程都会接着循环,都会得到池中线程池为10,然后10 > 8,所以timed为true。到if这里的时候,由于timed和timedOut都为true,所以timed && timedOut为true,于是我们成功进入到了if中,这里就会执行CAS操作,随机销毁一个线程。

被销毁的线程return走了,没有被销毁的线程通过continue接着循环,接着销毁线程,直到池中线程数wc不大于corePoolSize,也就是池中线程数等于核心线程数时,timed为false,无法进入if,进而执行try块。由于timed为false,所以这次进入try块执行的是workQueue.take()。

对于take方法,若是队列中有任务,那就获取任务并返回;若是队列中没有任务,线程就会阻塞等待,直到队列中有新的任务进来,该线程才会被唤醒并获取任务,获取成功后返回。

正是take方法阻塞式获取的特性,才不会让线程因为没有任务可执行而消失,这样就成功地保留住核心线程数了。

3、小结

若池中线程数小于核心线程数,执行的就是take,保证核心线程始终存活并等待任务;

若池中线程数大于核心线程数但线程又没有超时,执行的就是poll,执行过程中,要是在超时时间内获取到任务,则相安无事,要是超时无任务,那就触发线程回收,回收资源;

若池中线程数大于核心线程数并且线程超时,那执行的就是if语句中的CSA,随机销毁一个线程。

四、注意要点

1、Java自定义线程池与Tomcat线程池

这里需要注意的是,Java的自定义线程池和Tomcat的线程池是有一些差别的。

在Java的自定义线程池中,每次有新的请求过来,会判断池中线程数是否达到核心线程数,也会判断阻塞队列有没有满,还会判断池中线程数是否达到最大线程数,但唯独不会判断线程池中的线程是否空闲。也就是说,即使线程池中的线程都是空闲的,该请求都是先入队,然后被线程获取。

Tomcat的线程池就不一样了,每次有新的请求,除了上面所说的判断,它还会判断线程池中的线程是否空闲,若是有空闲的线程,这个请求就不需要入队了,直接让空闲的线程获取并处理。

2、workQueue.take与workQueue.poll

workQueue.take()其实和workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)差不太多,都是去阻塞队列获取任务。

若是阻塞队列不为空,两者都是直接获取任务,获取成功后返回。

若是阻塞队列为空,调用take方法的线程会进入阻塞状态,一直等待,直到队列中有新的任务进来,该线程才会被唤醒并获取任务,获取成功后返回。

面对阻塞队列为空的情况,调用poll方法的线程也会阻塞等待,但不是无休止地等待,等待时间不会超过指定的keepAliveTime。若是在超时时间内没有获取到任务,就会返回null,若是成功获取任务,那就返回。

http://www.dtcms.com/a/611104.html

相关文章:

  • AI入门知识之RAFT方法:基于微调的RAG优化技术详解
  • 怎么用word做一个网站网络企业做网站
  • 百度做网站教程房地产集团网站建设方案
  • 文心 5.0:原生全模态时代的技术分水岭
  • 多模式融合(GFS/GRAPES/ICON/GEM)在新能源预测中的对比与加权(工程版)
  • 25级第一次测试题解
  • 常用网站域名学做窗帘的网站
  • 网站制作基础教程网站建设的软件平台
  • MySQL数据库操作完全指南:从创建到管理的完整教程
  • C语言编译器在线编译 | 提供快速高效的C语言编译环境,适用于学习与开发
  • 临沂做网站的公司有哪些php网站开发难吗
  • epoll 事件全集、每个事件的含义、哪些事件在实际服务器中最常见、哪些会组合出现
  • 手机网站根目录建设银行登录网站
  • 磁共振成像原理(理论)31:基本梯度回波成像 (Basic Gradient-Echo Imaging)
  • 庐江县住房和城乡建设局网站网站建设的维护范围
  • ASC学习笔记0008:用于注册能力按键输入的回调
  • 邯郸市做网站广西远昌建设公司
  • 网站标题切换无版权的图片素材网站
  • Linux入门---vim编辑器
  • 网站建设服务代理商全面的上海代理注册公司
  • 8.Collections.synchronizedMap 与 ConcurrentMap 的区别与适用场景
  • PHP操作redis
  • Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(上)
  • 戴尔的网站建设福州网站建设营销方案
  • Redis 面试题精编(70道|含答案|分类整理)
  • 苏州做公司网站设计的公司网站建设项目执行进度表
  • 发布网站域名设置wordpress 搭建 查分系统
  • 四旋翼无人机视觉目标跟踪系统完整实现指南
  • 网站建设与管理案例柳洪轶苏州保洁公司招聘保洁区域经理
  • 中国核工业华兴建设有限公司网站c蔡甸区城乡建设局网站