ThreadPoolExecutor族谱

212 阅读4分钟

一、为什么要使用多线程?

线程可以比作是轻量级的进程,是程序执行的最小单位,线程间切换和调度的成本远远小于进程,使用多线程主要提升系能合理的利用CPU资源提升性能。

根据在开发中的场景,总结以下几个点:
1、避免阻塞 比如:在处理某个业务需求需要很长的时间,为此我们可以使用多线程提升性能;
2、异步调用 比如:需要执行A与B逻辑,在这个过程中不需要线性执行;
注:多线程是一把双刃剑,使用恰当可以提升性能,反之会有意向不到的并发问题,这就涉及对内存模型(JMM)的理解;

二、线程池

在阿里开发规约中强制要求 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程说明:
线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问
题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题;

2.1 创建线程池的方式?

在java8中线程池的类型分别有以为五种:

//创建单核心的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();

//创建固定核心数的线程池
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

//创建一个自动增长的线程池
ExecutorService executorService = Executors.newCachedThreadPool();

//创建一个按照计划计划执行的线程池
ExecutorService executorService = Executors.newScheduledThreadPool(threadCount);

//创建一个具有抢占式操作的线程池 
ExecutorService executorService = Executors.newWorkStealingPool();
可以在源码中可以看出前四种都是采用ThreadPoolExecutor的构造函数实现,为此来看看这个对象的构造函数:

public ThreadPoolExecutor(int corePoolSize,  //线程池长期维持的线程数
                          int maximumPoolSize,  //线程数的上限
                          long keepAliveTime,  //线程保持存活时间
                          TimeUnit unit,   //超过这个时间,多余的线程会被回收。
                          BlockingQueue<Runnable> workQueue,   // 用于存放任务的阻塞队列
                          ThreadFactory threadFactory,    // 新线程的产生方式
                          RejectedExecutionHandler handler) {    // 拒绝策略
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
corePoolSize:核心线程的数量,默认情况下,在创建线程池之后,线程池中的线程数为0,当有任务过来时,就会去创建线程执行任务,直到线程池中的线程数量达到corePoolSize之后,就会将任务放到缓存队列中;
maximumPoolSize:线程池中允许最大线程数,当缓存队列满了并且corePoolSize < maximumPoolSize时,就会创建新的线程执行任务;
keepAliveTime:当线程池中的线程数量 > 核心线程数量,就会按照keepAliveTime来判断是否退出线程;
unit:表示keepAliveTime的时间单位;
workQueue:用于存放任务的队列(下文会详细描述各种阻塞队列)
threadFactory:新线程的产生方式
handler:拒绝策略,如果workQueue队列已满,并且达到了maximumPoolSize上限,就会执行拒绝策略;

2.2 富四代的故事

为了加深对ThreadPoolExecutor的印象,我们来品一品它家族的故事;

ThreadPoolExecutor extends AbstractExecutorService (爸爸)

public abstract class AbstractExecutorService implements ExecutorService (祖父)

public interface ExecutorService extends Executor (曾祖父)

顶级接口 Executor (曾祖父)

  • 1、翻开族谱一看,ThreadPoolExecutor这个小伙子执行线程的方法execute()原来是他曾祖父声明,然后在ThreadPoolExecutor得到了具体实现;
  • 2、祖父ExecutorService为家族发扬广大,也声明了几个牛逼的招式,例如:带有回音的狮子吼 submit()、invokeAny()、invokeAll()、isShutdown()等;
  • 3、他爹也不甘落后,将爸爸传授的功夫几乎都得到具体的实现
  • 4、ThreadPoolExecutor这小伙子就牛逼了,直接给曾祖父的execute()给具体实现了,同时还兼容了祖父、爸爸的技能;

下文我们再继续品一品ThreadPoolExecutor是如何将狮子吼给学会的......

鄙人不才,在您面前献丑只愿与您结伴而行,文章若有不当之处,望大佬指点一二;如果我对您有帮助的话,还希望您能点赞分享,成长是一场苦涩的独自修行,我很需要您的陪伴与支持,这也是鄙人不断前行的根本动力,让我们在互相陪伴见证彼此生长的同时能够感染身边最亲近的人一同成长,鄙人在此叩谢!