当前位置:   article > 正文

Java线程池源码完全解析

java线程池源码

在源码解析之前,我们先思考一个问题:为什么要使用线程池?

如果不使用线程池,我们如何在程序中执行一些任务呢?

  1. 最显而易见的一种方式就是顺序执行,代码描述如下:
  1. java复制代码public static void main(String[] args) {
  2. doTask1();
  3. doTask2();
  4. // ...
  5. }
  1. 多线程执行,代码描述如下:
  1. java复制代码public static void main(String[] args) {
  2. new Thread(Demo::doTask1).start();
  3. new Thread(Demo::doTask2).start();
  4. // ...
  5. }

第一种方式编码简单,也不容易出错,但它的问题是效率不高,无法利用多核处理器的优势。第二种方式能利用多核的优势,为每个任务创建一个线程,由操作系统来调度任务,在任务不多的情况下它能很好的工作,但是当任务数量变多之后,线程创建销毁的性能损耗和线程的资源占用都将成为问题。

线程是宝贵的系统资源。

对于宝贵资源的使用,有一种通用的思想——池化。

这就是为什么我们要使用线程池的原因。下面这段代码是Java线程池ThreadPoolExecutor的基础使用案例。

  1. java复制代码public static void main(String[] args) {
  2. ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
  3. 4, // 核心池大小
  4. 6, // 最大池大小
  5. 1, SECONDS, // Worker线程超时时间
  6. new ArrayBlockingQueue<>(4), // 工作队列
  7. Thread::new, // 线程工厂
  8. new ThreadPoolExecutor.AbortPolicy() // 拒绝任务的策略
  9. );
  10. // 提交task
  11. threadPool.execute(Demo::doTask1);
  12. threadPool.execute(Demo::doTask2);
  13. // ...
  14. threadPool.shutdown();
  15. }

接下来我们根据上面这段示例代码,深入到源码,分析一下线程池的实现原理。

ThreadPoolExecutor的构造

上面的代码展示了ThreadPoolExecutor最基础的构造方法,一共有6个参数(明明是7个...),构造方法里面都是一些初始化操作,不在赘述,重点关注一下这6个参数。这里我先列举这些核心参数的用途,在后面源码分析的过程中我们会频繁看到这些参数的身影。

  • corePoolSize 线程池正常运行时池的大小,这里称之为核心池
  • maximumPoolSize 线程池能扩展到的最大线程数量,在核心池和工作队列都满的时候扩展,为了方便理解,这里将扩展出来的部分称之为临时池,也就是线程池=核心池+临时池
  • keepAliveTime + TimeUnit 临时池或者核心池(当设置allowCoreThreadTimeOut为true时)线程超时时间,线程超过这个时间没有处理任务就会退出
  • workQueue 工作队列(阻塞的),核心池的Worker线程全部启动的情况下,会将任务放到这个队列
  • threadFactory 线程工厂,用于创建Worker线程
  • RejectedExecutionHandler 拒绝策略,当核心池、工作队列和临时池都满了的情况下,或者线程池不是RUNNING状态都会调用拒绝策略

提交的任务是如何被执行的

ThreadPoolExecutor提交任务的核心方法只有一个,就是execute方法。其他的如submit、invoke方法都是在其父类AbstractExecutorService中使用模板方法模式实现的,归根结底还是调用了execute方法。

所以我们重点关注execute方法提交一个任务后都发生了什么?

  1. java复制代码public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4. int c = ctl.get();
  5. if (workerCountOf(c) < corePoolSize) {
  6. if (addWorker(command, true)) // ① 核心池还没有满
  7. return;
  8. c = ctl.get();
  9. }
  10. if (isRunning(c) && workQueue.offer(command)) { // ② 核心池满了,工作队列没满
  11. int recheck = ctl.get();
  12. if (! isRunning(recheck) && remove(command))
  13. reject(command); // 线程池不是RUNNING状态了,执行拒绝策略
  14. else if (workerCountOf(recheck) == 0)
  15. // 重新检查,核心池线程可能挂了,添加一个新的,
  16. // 但是不用设置firstTask(因为task已经添加到workQueue了)
  17. addWorker(null, false);
  18. }
  19. else if (!addWorker(command, false)) // ③ 核心池和工作队列都满了
  20. reject(command); // 全都满了,或者线程池已经不是RUNNING状态了,执行拒绝策略
  21. }

从上面的源码可以看出,提交一个任务后,主要有3个分支,接下来我们详细分析这3种情况都做了哪些事情。

1. 核心池还没有满

此时workerCount < corePoolSize,工作队列还是空的。

通过上面execute的源码①的位置,调用了addWorker方法,其源码如下所示。这个方法比较长,我添加了比较详细的注释,后面还有很多地方会遇到这个方法。

  1. java复制代码private boolean addWorker(Runnable firstTask, boolean core) {
  2. retry:
  3. for (;;) {
  4. int c = ctl.get();
  5. int rs = runStateOf(c);
  6. // ① 线程池状态不是RUNNING的时候不能添加新的Worker线程
  7. /
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/971126
推荐阅读
相关标签
  

闽ICP备14008679号