我们知道如果程序中并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束时,会因为频繁创建线程而大大降低系统的效率,因此出现了线程池的使用方式,它可以提前创建好线程来执行任务。本文主要通过java的ThreadPoolExecutor来查看线程池的内部处理过程。

1 ThreadPoolExecutor

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,下面我们来看一下ThreadPoolExecutor类的部分实现源码。

1.1 构造方法

ThreadPoolExecutor类提供了如下4个构造方法

  1. // 设置线程池时指定核心线程数、最大线程数、线程存活时间及等待队列。
  2. // 线程创建工厂和拒绝策略使用默认的(AbortPolicy)
  3. public ThreadPoolExecutor(int corePoolSize,
  4. int maximumPoolSize,
  5. long keepAliveTime,
  6. TimeUnit unit,
  7. BlockingQueue<Runnable> workQueue) {
  8. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  9. Executors.defaultThreadFactory(), defaultHandler);
  10. }
  11. // 设置线程池时指定核心线程数、最大线程数、线程存活时间、等待队列及线程创建工厂
  12. // 拒绝策略使用默认的(AbortPolicy)
  13. public ThreadPoolExecutor(int corePoolSize,
  14. int maximumPoolSize,
  15. long keepAliveTime,
  16. TimeUnit unit,
  17. BlockingQueue<Runnable> workQueue,
  18. ThreadFactory threadFactory) {
  19. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  20. threadFactory, defaultHandler);
  21. }
  22. // 设置线程池时指定核心线程数、最大线程数、线程存活时间、等待队列及拒绝策略
  23. // 线程创建工厂使用默认的
  24. public ThreadPoolExecutor(int corePoolSize,
  25. int maximumPoolSize,
  26. long keepAliveTime,
  27. TimeUnit unit,
  28. BlockingQueue<Runnable> workQueue,
  29. RejectedExecutionHandler handler) {
  30. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  31. Executors.defaultThreadFactory(), handler);
  32. }
  33. // 设置线程池时指定核心线程数、最大线程数、线程存活时间、等待队列、线程创建工厂及拒绝策略
  34. public ThreadPoolExecutor(int corePoolSize,
  35. int maximumPoolSize,
  36. long keepAliveTime,
  37. TimeUnit unit,
  38. BlockingQueue<Runnable> workQueue,
  39. ThreadFactory threadFactory,
  40. RejectedExecutionHandler handler) {
  41. if (corePoolSize < 0 ||
  42. maximumPoolSize <= 0 ||
  43. maximumPoolSize < corePoolSize ||
  44. keepAliveTime < 0)
  45. throw new IllegalArgumentException();
  46. if (workQueue == null || threadFactory == null || handler == null)
  47. throw new NullPointerException();
  48. this.acc = System.getSecurityManager() == null ?
  49. null :
  50. AccessController.getContext();
  51. this.corePoolSize = corePoolSize;
  52. this.maximumPoolSize = maximumPoolSize;
  53. this.workQueue = workQueue;
  54. this.keepAliveTime = unit.toNanos(keepAliveTime);
  55. this.threadFactory = threadFactory;
  56. this.handler = handler;
  57. }

通过观察上述每个构造器的源码实现,我们可以发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

下面解释一下构造器中各个参数的含义:

  • corePoolSize:核心池的线程个数上线,在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
  • maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程。
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。
  • unit:参数keepAliveTime的时间单位。
  • workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响;
  • threadFactory:线程工厂,主要用来创建线程;
  • handler:表示当拒绝处理任务时的策略。有以下四种取值:ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

1.2 核心方法

在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法。

  1. public void execute(Runnable command) {
  2. // 判断提交的任务command是否为null,若是null,则抛出空指针异常;
  3. if (command == null)
  4. throw new NullPointerException();
  5. // 获取线程池中当前线程数
  6. int c = ctl.get();
  7. // 如果线程池中当前线程数小于核心池大小,进入if语句块
  8. if (workerCountOf(c) < corePoolSize) {
  9. // 如果以给定的命令启动一个核心线程执行任务成功,直接返回
  10. if (addWorker(command, true))
  11. return;
  12. c = ctl.get();
  13. }
  14. // 如果当前线程池处于RUNNING状态,则将任务放入任务缓存队列
  15. if (isRunning(c) && workQueue.offer(command)) {
  16. int recheck = ctl.get();
  17. // 如果线程池不处于运行状态并且移除刚加入的任务成功则执行拒绝策略
  18. if (! isRunning(recheck) && remove(command))
  19. reject(command);
  20. // 如果当前线程数为0,则在线程池里增加一个线程,保证队列里的任务不会没有线程执行
  21. else if (workerCountOf(recheck) == 0)
  22. addWorker(null, false);
  23. }
  24. // 尝试启动核心线程之外的线程,如果不满足,则执行对应的拒绝策略
  25. else if (!addWorker(command, false))
  26. reject(command);
  27. }

主要方法addWorker。

  1. private boolean addWorker(Runnable firstTask, boolean core) {
  2. retry:
  3. for (;;) {
  4. int c = ctl.get();
  5. int rs = runStateOf(c);
  6. // 如果线程池状态大于SHUTDOWN或者线程池状态等于SHUTDOWN,firstTask不等于null
  7. // 或者线程池状态等于SHUTDOWN,任务队列等于空时,直接返回false结束。
  8. if (rs >= SHUTDOWN &&
  9. ! (rs == SHUTDOWN &&
  10. firstTask == null &&
  11. ! workQueue.isEmpty()))
  12. return false;
  13. for (;;) {
  14. int wc = workerCountOf(c);
  15. // 如果线程数量大于等于最大数量或者大于等于上限
  16. //(入参core传true,取核心线程数,否则取最大线程数),直接返回false结束。
  17. if (wc >= CAPACITY ||
  18. wc >= (core ? corePoolSize : maximumPoolSize))
  19. return false
  20. // CAS操作给工作线程数加1,成功则跳到retry处,不再进入循环。
  21. if (compareAndIncrementWorkerCount(c))
  22. break retry;
  23. c = ctl.get(); // Re-read ctl
  24. // 如果线程池状态与刚进入时不一致,则跳到retry处,再次进入循环
  25. if (runStateOf(c) != rs)
  26. continue retry;
  27. // else CAS failed due to workerCount change; retry inner loop
  28. }
  29. }
  30. boolean workerStarted = false;
  31. boolean workerAdded = false;
  32. Worker w = null;
  33. try {
  34. // 新建一个线程
  35. w = new Worker(firstTask);
  36. final Thread t = w.thread;
  37. if (t != null) {
  38. final ReentrantLock mainLock = this.mainLock;
  39. mainLock.lock();
  40. try {
  41. int rs = runStateOf(ctl.get());
  42. // 如果线程池状态在SHUTDOWN之前或者
  43. // 线程池状态等于SHUTDOWN并且firstTask等于null时,进入处理。
  44. if (rs < SHUTDOWN ||
  45. (rs == SHUTDOWN && firstTask == null)) {
  46. // 如果要执行的线程正在运行,则抛异常
  47. if (t.isAlive()) // precheck that t is startable
  48. throw new IllegalThreadStateException();
  49. workers.add(w);
  50. int s = workers.size();
  51. if (s > largestPoolSize)
  52. largestPoolSize = s;
  53. workerAdded = true;
  54. }
  55. } finally {
  56. mainLock.unlock();
  57. }
  58. if (workerAdded) {
  59. // 启动线程
  60. t.start();
  61. workerStarted = true;
  62. }
  63. }
  64. } finally {
  65. // 如果线程添加失败,则将新增的对应信息删除
  66. if (! workerStarted)
  67. addWorkerFailed(w);
  68. }
  69. return workerStarted;
  70. }

1.3 任务执行run方法

在上述addWorker中,当调用线程的start方法启动线程后,会执行其中的run方法。

  1. public void run() {
  2. runWorker(this);
  3. }
  4. final void runWorker(Worker w) {
  5. Thread wt = Thread.currentThread();
  6. Runnable task = w.firstTask;
  7. w.firstTask = null;
  8. w.unlock(); // allow interrupts
  9. boolean completedAbruptly = true;
  10. try {
  11. // 如果任务不为空或者新获取到的任务不为空
  12. while (task != null || (task = getTask()) != null) {
  13. w.lock();
  14. // 当线程池状态,大于等于 STOP 时,保证工作线程都有中断标志。
  15. // 当线程池状态,小于STOP时,保证工作线程都没有中断标志。
  16. if ((runStateAtLeast(ctl.get(), STOP) ||
  17. (Thread.interrupted() &&
  18. runStateAtLeast(ctl.get(), STOP))) &&
  19. !wt.isInterrupted())
  20. wt.interrupt();
  21. try {
  22. beforeExecute(wt, task);
  23. Throwable thrown = null;
  24. try {
  25. // 执行任务
  26. task.run();
  27. } catch (RuntimeException x) {
  28. thrown = x; throw x;
  29. } catch (Error x) {
  30. thrown = x; throw x;
  31. } catch (Throwable x) {
  32. thrown = x; throw new Error(x);
  33. } finally {
  34. afterExecute(task, thrown);
  35. }
  36. } finally {
  37. task = null;
  38. w.completedTasks++;
  39. w.unlock();
  40. }
  41. }
  42. completedAbruptly = false;
  43. } finally {
  44. processWorkerExit(w, completedAbruptly);
  45. }
  46. }

2 整体处理过程

通过上述源码分析,我们可以得出线程池处理任务的过程如下:

3 总结

本文从源码层面主要分析了线程池的创建、运行过程,通过上述的分析,可以看出当线程池中的线程数量超过核心线程数后,会先将任务放入等待队列,队列放满后当最大线程数大于核心线程数时,才会创建新的线程执行。

作者:京东物流 管碧强

来源:京东云开发者社区 自猿其说Tech 转载请注明来源

ThreadPoolExecutor线程池内部处理浅析的更多相关文章

  1. j.u.c系列(01) ---初探ThreadPoolExecutor线程池

    写在前面 之前探索tomcat7启动的过程中,使用了线程池(ThreadPoolExecutor)的技术 public void createExecutor() { internalExecutor ...

  2. Java ThreadPoolExecutor线程池原理及源码分析

    一.源码分析(基于JDK1.6) ThreadExecutorPool是使用最多的线程池组件,了解它的原始资料最好是从从设计者(Doug Lea)的口中知道它的来龙去脉.在Jdk1.6中,Thread ...

  3. Java并发——ThreadPoolExecutor线程池解析及Executor创建线程常见四种方式

    前言: 在刚学Java并发的时候基本上第一个demo都会写new Thread来创建线程.但是随着学的深入之后发现基本上都是使用线程池来直接获取线程.那么为什么会有这样的情况发生呢? new Thre ...

  4. 十、自定义ThreadPoolExecutor线程池

    自定义ThreadPoolExecutor线程池 自定义线程池需要遵循的规则 [1]线程池大小的设置 1.计算密集型: 顾名思义就是应用需要非常多的CPU计算资源,在多核CPU时代,我们要让每一个CP ...

  5. 源码剖析ThreadPoolExecutor线程池及阻塞队列

    本文章对ThreadPoolExecutor线程池的底层源码进行分析,线程池如何起到了线程复用.又是如何进行维护我们的线程任务的呢?我们直接进入正题: 首先我们看一下ThreadPoolExecuto ...

  6. 13.ThreadPoolExecutor线程池之submit方法

    jdk1.7.0_79  在上一篇<ThreadPoolExecutor线程池原理及其execute方法>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法 ...

  7. ThreadPoolExecutor 线程池的源码解析

    1.背景介绍 上一篇从整体上介绍了Executor接口,从上一篇我们知道了Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newSchedul ...

  8. ThreadPoolExecutor 线程池

    TestThreadPoolExecutorMain package core.test.threadpool; import java.util.concurrent.ArrayBlockingQu ...

  9. Executors、ThreadPoolExecutor线程池讲解

    官方+白话讲解Executors.ThreadPoolExecutor线程池使用 Executors:JDK给提供的线程工具类,静态方法构建线程池服务ExecutorService,也就是Thread ...

  10. SpringBoot项目框架下ThreadPoolExecutor线程池+Queue缓冲队列实现高并发中进行下单业务

    主要是自己在项目中(中小型项目) 有支付下单业务(只是办理VIP,没有涉及到商品库存),目前用户量还没有上来,目前没有出现问题,但是想到如果用户量变大,下单并发量变大,可能会出现一系列的问题,趁着空闲 ...

随机推荐

  1. auto-GPT部署

    Auto-GPT 是一个实验性开源应用程序,其作者在3月31日将其发布在Github上.它以GPT-4 作为驱动,可以自主做出决定以实现目标,无需用户干预.AutoGPT的地址:https://git ...

  2. Web通用漏洞--文件上传

    Web通用漏洞--文件上传 概述 文件上传安全指的是攻击者通过利用上传实现后门的写入连接后门进行权限控制的安全问题,对于如何确保这类安全问题,一般会从原生态功能中的文件内容,文件后缀,文件类型等方面判 ...

  3. Web通用漏洞--文件包含

    Web通用漏洞--文件包含 文件包含原理 在项目开发过程中,开发人员通常会将重复使用的函数写入单个文件中,在使用该类函数时,直接调用文件即可,无需重新编写,这种调用文件的过程成为文件包含.在文件包含过 ...

  4. 【技术积累】Linux中的命令行【理论篇】【九】

    blkid命令 命令介绍 blkid命令是一个用于查看块设备属性的Linux命令.它可以识别和显示块设备的文件系统类型.UUID.LABEL.PARTUUID等信息. 命令说明 在Linux下可以使用 ...

  5. python列表的增删

    list = [1, 2, 3, 4]# 打印后两位print(list[-2:])# 打印前2位print(list[:2])# 修改列表元素list[0] = 5print(list)# 添加元素 ...

  6. 【krpano】淘宝buy+案例

    这是一个类似淘宝buy+的案例,是基于krpano全景开发工具二次开发的全景视频.WebVR.360°环物.全景视频热点添加于一身的综合性案例.现在将案例上传网站供krpano技术人员和爱好者大家共同 ...

  7. Web端上传数据到OSS

    阿里云文档:参考文献 更正第三点:用户带着从服务器获取的数据签名和文件上传到OSS,这样做可以保证安全性.减轻服务器负担. 1.操作步骤 ①新建Bucket ②创建后更改跨域设置 这一步是保证跨域请胯 ...

  8. Vue 搭配 Spring MVC 创建一个 web 项目

    Vue 搭配 Spring MVC 创建一个 web 项目 想要写一个登录的web应用程序.页面使用Vue,后端使用Spring MVC,最终打成war包,放在tomcat下启动. 1.创建Sprin ...

  9. MySQL快速导入千万条数据(2)

    目录 一.导入前1000万条数据 二.导入前2000万条数据 三.导入后面的1000万条数据 四.建索引 五.总结 接上文,继续测试3000万条记录快速导入数据库. 一.导入前1000万条数据 清库. ...

  10. ciscn_2019_c_1 题解

    main函数如下: int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [rsp+Ch] [rb ...