java线程池原理 深入源码探险才知道

2018-10-0909:03:43编程语言入门到精通Comments2,740 views字数 3118阅读模式

程序运行,其本质上,是对系统资源(CPU、内存、磁盘、网络等等)的使用。如何高效的使用这些资源是我们编程优化演进的一个方向。今天说的线程池是对CPU的利用的优化手段。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

网上有不少介绍如何使用线程池的文章,那我想说点什么呢?我希望查看线程池原理,明白池化技术的基本设计思路。遇到其他相似问题可以解决。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

池化技术

何为池化技术,简单点来说,就是提前保存大量的资源,以备不时之需。在资源有限的情况下,该技术可以大大提升资源的利用率,提升性能等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

目前比较典型的池化技术有: 线程池、连接池、内存池、对象池等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

本文主要来介绍一下其中比较简单的线程池的实现原理,希望读者们可以举一反三,通过对线程池的理解,学习并掌握所有的编程中池化技术的底层原理,一通百通。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

创建一个线程

在java的并发编程中,线程是十分重要的,在Java中,创建一个线程比较简单:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

public class App {
    public static void main(String[] args) throws Exception {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程运行中");
            }
        }).start();
    }
}
复制代码

我们通过创建一个线程对象,并且实现Runnable接口就可以实现一个简单的线程。可以利用上多核CPU。当一个任务结束,当前线程就结束。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

但很多时候,我们不止会执行一个任务。如果每次都是如此的创建线程->执行任务->销毁线程,会造成很大的性能开销的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

那能否一个线程创建后,执行完一个任务后,又去执行另一个任务,而不是销毁。这就是线程池。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

这就是池化技术的思想,通过预先创建好多个线程,放在池中,这样可以在需要使用线程的时候直接获取,避免多次重复创建、销毁代理的开销。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

线程池的简单使用

以下代码,就是在java中创建线程池:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

import java.util.concurrent.*;

public class App {
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = new ThreadPoolExecutor(1, 1,
                60L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10));

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("abcdefg");
            }
        });

        executorService.shutdown();
    }
}
复制代码

JDK提供给外部的接口也很简单。直接调用ThreadPoolExecutor构造一个就可以了,也可以通过Executors静态工厂构建,但一般不建议。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

可以看到,开发者想要在代码中使用线程池还是比较简单的,这得益于Java给我们封装好了一系列的API。很多时候,我们需要知道这些API后面干了些啥,以便于我们更好的设计与实现我们的代码。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

线程池构造函数

通常,一般构造函数会反映出这个工具或这个对象的数据存储结构。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

java线程池原理 深入源码探险才知道

如果把线程池比作一个公司。公司会有正式员工处理正常业务,如果工作量大的话,会雇佣外包人员来工作。闲时就可以释放外包人员以减少公司管理开销。一个公司因为成本关系,雇佣的人员始终是有最大数。如果这时候还有任务处理不过来,就走需求池排任务。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

  • acc : 获取调用上下文
  • corePoolSize: 核心线程数量,可以类比正式员工数量,常驻线程数量。
  • maximumPoolSize: 最大的线程数量,公司最多雇佣员工数量。常驻+临时线程数量。
  • workQueue:多余任务等待队列,再多的人都处理不过来了,需要等着,在这个地方等。
  • keepAliveTime:非核心线程空闲时间,就是外包人员等了多久,如果还没有活干,解雇了。
  • threadFactory: 创建线程的工厂,在这个地方可以统一处理创建的线程的属性。每个公司对员工的要求不一样,恩,在这里设置员工的属性。
  • handler:线程池拒绝策略,什么意思呢?就是当任务实在是太多,人也不够,需求池也排满了,还有任务咋办?默认是不处理,抛出异常告诉任务提交者,我这忙不过来了。

添加一个任务

java线程池原理 深入源码探险才知道

核心模块用红框标记了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

  • 第一个红框:判断工作的线程数有没有超过最大核心线程数量,如果没有超过新镇新的worker线程。并且推出。
  • 第二个红框:判断线程池是否在运行,如果在,任务队列是否允许插入,插入成功再次验证线程池是否运行,如果不再运行,移除插入的任务,然后抛出拒绝策略。如果再运行,没有线程了,就启用一个线程。
  • 第三个红框:如果添加非核心线程失败,就直接拒绝了。

这里逻辑稍微有点复杂,画了个流程图仅供参考文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

java线程池原理 深入源码探险才知道

接下来,我们看看如何添加一个工作线程的? addWork文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

添加worker线程

java线程池原理 深入源码探险才知道

这里代码有点长,没关系,也是分块的,总共有5个关键的代码块。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

  • 第一个红框:做是否能够添加工作线程条件过滤。
    • 判断线程池状态,如果线程池已经关了,就不提交任务了。
  • 第二个红框:做自旋,更新创建线程数量。
    java线程池原理 深入源码探险才知道" alt="添加work2" data-data-original="https://user-gold-cdn.xitu.io/2018/10/8/16651a9f468b287a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://www.cainiaoxueyuan.com/wp-content/themes/begin/img/loading.png"height="20" data-width="1157" data-height="805" />
  • 第一个红框:获取线程池主锁。
  • 第二个红框:添加线程到workers中(线程池中)。
  • 第三个红框:启动新建的线程。 这样就应该清晰很多了。

有人或许会疑问 retry 是什么?这个是java中的goto语法。只能运用在break和continue后面。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

接下来,我们看看works是什么。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

java线程池原理 深入源码探险才知道" alt="works" data-data-original="https://user-gold-cdn.xitu.io/2018/10/8/16651a9f4677c247?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://www.cainiaoxueyuan.com/wp-content/themes/begin/img/loading.png"height="20" data-width="762" data-height="181" />

一个hashSet。 到这来就完成了一个任务的提交。当一个线程完成了首次任务的执行,后续如何处理其他的请求的呢?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

worker线程处理队列任务

java线程池原理 深入源码探险才知道
  • 第一个红框:是否是第一次执行任务,或者从队列中可以获取到任务。
  • 第二个红框:获取到任务后,执行任务开始前操作钩子。
  • 第三个红框:执行任务。
  • 第四个红框:执行任务后钩子。

这两个钩子(beforeExecute,afterExecute)允许我们自己继承线程池,做任务执行前后处理。有意思。 到这里,源代码分析到此为止。接下来做一下简单的总结。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

总结

  1. 所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。
  2. 只有当阻塞队列满了后,才会触发非核心线程的创建。所以非核心线程只是临时过来打杂的。直到空闲了,然后自己关闭了。
  3. 线程池提供了两个钩子(beforeExecute,afterExecute)给我们,我们继承线程池,在执行任务前后做一些事情。
  4. 线程池原理关键技术:锁(lock,cas)、阻塞队列、hashSet(资源池)
java线程池原理 深入源码探险才知道

最后希望对你理解线程池有帮助。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

作者:林湾村龙猫
链接:https://juejin.im/post/5bbacb2cf265da0add51d70a
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html

文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/6474.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/6474.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定