Tomcat修正JDK原生线程池bug的实现原理

为提高处理能力和并发度,Web容器一般会把处理请求的任务放到线程池,而JDK的原生线程池先天适合CPU密集型任务,于是Tomcat改造之。

Tomcat 线程池原理

其实ThreadPoolExecutor的参数主要有如下关键点:

限制线程个数

限制队列长度

而Tomcat对这俩资源都需要限制,否则高并发下CPU、内存都有被耗尽可能。
因此Tomcat的线程池传参:

// 定制的任务队列
taskqueue = new TaskQueue(maxQueueSize);

// 定制的线程工厂
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,
							                 daemon,
							                 getThreadPriority()
);

// 定制线程池
executor = new ThreadPoolExecutor(getMinSpareThreads(),
								  getMaxThreads(),
				 			      maxIdleTime,
				 			      TimeUnit.MILLISECONDS,
				 			      taskqueue,
				 			      tf);

Tomcat对线程数也有限制,设置:

  • 核心线程数(minSpareThreads)
  • 最大线程池数(maxThreads)

Tomcat线程池还有自己的特色任务处理流程,通过重写execute方法实现了自己的特色任务处理逻辑:

  1. 前corePoolSize个任务时,来一个任务就创建一个新线程
  2. 再有任务,就把任务放入任务队列,让所有线程去抢。若队列满,就创建临时线程
  3. 总线程数达到maximumPoolSize,则继续尝试把任务放入任务队列
  4. 若缓冲队列也满了,插入失败,执行拒绝策略

和 JDK 线程池的区别就在step3,Tomcat在线程总数达到最大数时,不是立即执行拒绝策略,而是再尝试向任务队列添加任务,添加失败后再执行拒绝策略。

具体又是如何实现的呢?

public void execute(Runnable command, long timeout, TimeUnit unit) {
    submittedCount.incrementAndGet();
    try {
        // 调用JDK原生线程池的execute执行任务
        super.execute(command);
    } catch (RejectedExecutionException rx) {
       // 总线程数达到maximumPoolSize后,JDK原生线程池会执行默认拒绝策略
        if (super.getQueue() instanceof TaskQueue) {
            final TaskQueue queue = (TaskQueue)super.getQueue();
            try {
                // 继续尝试把任务放入任务队列
                if (!queue.force(command, timeout, unit)) {
                    submittedCount.decrementAndGet();
                    // 若缓冲队列还是满了,插入失败,执行拒绝策略。
                    throw new RejectedExecutionException("...");
                }
            }
        }
    }
}

定制任务队列

Tomcat线程池的execute方法第一行:

submittedCount.incrementAndGet();

任务执行失败,抛异常时,将该计数器减一:

submittedCount.decrementAndGet();

Tomcat线程池使用 submittedCount 变量维护已提交到线程池,但未执行完的任务数量。

为何要维护这样一个变量呢?

Tomcat的任务队列TaskQueue扩展了JDK的LinkedBlockingQueue,Tomcat给了它一个capacity,传给父类LinkedBlockingQueue的构造器。

public class TaskQueue extends LinkedBlockingQueue<Runnable> {

  public TaskQueue(int capacity) {
      super(capacity);
  }
  ...
}

capacity参数通过Tomcat的maxQueueSize参数设置,但maxQueueSize默认值Integer.MAX_VALUE:当前线程数达到核心线程数后,再来任务的话线程池会把任务添加到任务队列,并且总会成功,就永远无机会创建新线程了。

为解决该问题,TaskQueue重写了LinkedBlockingQueue#offer,在合适时机返回false,表示任务添加失败,这时线程池就会创建新线程。

什么叫合适时机?

public class TaskQueue extends LinkedBlockingQueue<Runnable> {

  ...
   @Override
  // 线程池调用任务队列的方法时,当前线程数 > core线程数
  public boolean offer(Runnable o) {

      // 若线程数已达max,则不能创建新线程,只能放入任务队列
      if (parent.getPoolSize() == parent.getMaximumPoolSize())
          return super.offer(o);

      // 至此,表明 max线程数 > 当前线程数 > core线程数
      // 说明可创建新线程:

      // 1. 若已提交任务数 < 当前线程数
      //    表明还有空闲线程,无需创建新线程
      if (parent.getSubmittedCount()<=(parent.getPoolSize()))
          return super.offer(o);

      // 2. 若已提交任务数 > 当前线程数
      //    线程不够用了,返回false去创建新线程
      if (parent.getPoolSize()<parent.getMaximumPoolSize())
          return false;

      // 默认情况下总是把任务放入任务队列
      return super.offer(o);
  }

}

所以Tomcat维护 已提交任务数 是为了在任务队列长度无限时,让线程池还能有机会创建新线程。

到此这篇关于Tomcat是如何修正JDK原生线程池bug的的文章就介绍到这了,更多相关Tomcat JDK原生线程池内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于tomcat的连接数与线程池详解

    前言 在使用tomcat时,经常会遇到连接数.线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector). 在前面的文章 详解Tomcat配置文件server.xml 中写到过:Connector的主要功能,是接收连接请求,创建Request和Response对象用于和请求端交换数据:然后分配线程让Engine(也就是Servlet容器)来处理这个请求,并把产生的Request和Response对象传给Engine.当Engine处理完请求后,也会通过Conn

  • JDK线程池和Spring线程池的使用实例解析

    这篇文章主要介绍了JDK线程池和Spring线程池的使用实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 JDK线程池和Spring线程池实例,异步调用,可以直接使用 (1)JDK线程池的使用,此处采用单例的方式提供,见示例: public class ThreadPoolUtil { private static int corePoolSize = 5; private static int maximumPoolSize = 10;

  • Tomcat使用线程池处理远程并发请求的方法

    通过了解学习tomcat如何处理并发请求,了解到线程池,锁,队列,unsafe类,下面的主要代码来自 java-jre: sun.misc.Unsafe java.util.concurrent.ThreadPoolExecutor java.util.concurrent.ThreadPoolExecutor.Worker java.util.concurrent.locks.AbstractQueuedSynchronizer java.util.concurrent.locks.Abstr

  • jdk自带线程池实例详解

    二.简介 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力,但频繁的创建线程的开销是很大的,那么如何来减少这部分的开销了,那么就要考虑使用线程池了.线程池就是一个线程的容器,每次只执行额定数量的线程,线程池就是用来管理这些额定数量的线程. 三.涉及线程池的类结构图 其中供我们使用的,主要是ThreadPoolExecutor类. 四.如何创建线程池 我们创建线程池一般有以下几种方法: 1.使用Executors工厂类 Executor

  • Tomcat修正JDK原生线程池bug的实现原理

    为提高处理能力和并发度,Web容器一般会把处理请求的任务放到线程池,而JDK的原生线程池先天适合CPU密集型任务,于是Tomcat改造之. Tomcat 线程池原理 其实ThreadPoolExecutor的参数主要有如下关键点: 限制线程个数 限制队列长度 而Tomcat对这俩资源都需要限制,否则高并发下CPU.内存都有被耗尽可能. 因此Tomcat的线程池传参: // 定制的任务队列 taskqueue = new TaskQueue(maxQueueSize); // 定制的线程工厂 Ta

  • Java手写线程池之向JDK线程池进发

    目录 前言 JDK线程池一瞥 自己动手实现线程池 线程池参数介绍 实现Runnable 实现Callable 拒绝策略的实现 线程池关闭实现 工作线程的工作实现 线程池实现的BUG 完整代码 线程池测试 总结 前言 在前面的文章自己动手写乞丐版线程池中,我们写了一个非常简单的线程池实现,这个只是一个非常简单的实现,在本篇文章当中我们将要实现一个和JDK内部实现的线程池非常相似的线程池. JDK线程池一瞥 我们首先看一个JDK给我们提供的线程池ThreadPoolExecutor的构造函数的参数:

  • Java自定义线程池的实现示例

    目录 一.Java语言本身也是多线程,回顾Java创建线程方式如下: 二.JDK线程池工具类. 三.业界知名自定义线程池扩展使用. 一.Java语言本身也是多线程,回顾Java创建线程方式如下: 1.继承Thread类,(Thread类实现Runnable接口),来个类图加深印象. 2.实现Runnable接口实现无返回值.实现run()方法,啥时候run,黑话了. 3.实现Callable接口重写call()+FutureTask获取. public class CustomThread {

  • Java实现终止线程池中正在运行的定时任务

    最近项目中遇到了一个新的需求,就是实现一个可以动态添加定时任务的功能.说到这里,有人可能会说简单啊,使用quartz就好了,简单粗暴.然而quartz框架太重了,小项目根本不好操作啊.当然,也有人会说,jdk提供了timer的接口啊,完全够用啊.但是我们项目的需求完全是多线程的模型啊,而timer是单线程的,so,楼主最后还是选择了jdk的线程池. 线程池是什么 Java通过Executors提供四种线程池,分别为: newCachedThreadPool :创建一个可缓存线程池,如果线程池长度

  • Java并发之线程池Executor框架的深入理解

    线程池 无限制的创建线程 若采用"为每个任务分配一个线程"的方式会存在一些缺陷,尤其是当需要创建大量线程时: 线程生命周期的开销非常高 资源消耗 稳定性 引入线程池 任务是一组逻辑工作单元,线程则是使任务异步执行的机制.当存在大量并发任务时,创建.销毁线程需要很大的开销,运用线程池可以大大减小开销. Executor框架 说明: Executor 执行器接口,该接口定义执行Runnable任务的方式. ExecutorService 该接口定义提供对Executor的服务. Sched

  • 深度源码解析Java 线程池的实现原理

    java 系统的运行归根到底是程序的运行,程序的运行归根到底是代码的执行,代码的执行归根到底是虚拟机的执行,虚拟机的执行其实就是操作系统的线程在执行,并且会占用一定的系统资源,如CPU.内存.磁盘.网络等等.所以,如何高效的使用这些资源就是程序员在平时写代码时候的一个努力的方向.本文要说的线程池就是一种对 CPU 利用的优化手段. 线程池,百度百科是这么解释的: 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的

  • 实现java简单的线程池

    目录 拆分实现流程 实现方式 1.拒绝策略 2.阻塞队列 3.线程池和工作线程 策略模式 对比JDK的线程池 线程池的状态转化 总结 拆分实现流程 请看下面这张图 首先我们得对线程池进行一个功能拆分 Thread Pool 就是我们的线程池,t1,t2,t3代表三个线程 Blocking Queue代表阻塞队列 main代表main方法的线程 task1,task2,task3代表要执行的每个任务 现在我们梳理一下执行的流程,注意这里是简略版的,文章后面我会给出详细版的 所以此时,我们发现了需要

  • Java实现手写一个线程池的示例代码

    目录 概述 线程池框架设计 代码实现 阻塞队列的实现 线程池消费端实现 获取任务超时设计 拒绝策略设计 概述 线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和细节吗?如果直接去看jdk源码的话,可能有一定的难度,那么我们可以先通过手写一个简单的线程池框架,去掌握线程池的基本原理后,再去看jdk的线程池源码就会相对容易,而且不容易忘记. 线程池框架设计 我们都知道,线程资源的创建和销毁并不是没有代价的,甚至开销是非常高的.同

  • Java实现手写乞丐版线程池的示例代码

    目录 前言 线程池的具体实现 线程池实现思路 线程池实现代码 线程池测试代码 杂谈 总结 前言 在上篇文章线程池的前世今生当中我们介绍了实现线程池的原理,在这篇文章当中我们主要介绍实现一个非常简易版的线程池,深入的去理解其中的原理,麻雀虽小,五脏俱全. 线程池的具体实现 线程池实现思路 任务保存到哪里? 在上篇文章线程池的前世今生当中我们具体去介绍了线程池当中的原理.在线程池当中我们有很多个线程不断的从任务池(用户在使用线程池的时候不断的使用execute方法将任务添加到线程池当中)里面去拿任务

随机推荐