别再问我能不能自己写个线程池了

362 阅读6分钟

上篇文章我们讲了java中四种线程池的使用方式和它们之间的区别,不清楚的可以去看一下:www.jianshu.com/p/3282f6f7e… 那么线程池的底层是如何实现的呢?我们可不可以根据需求自己来实现一个定制的线程池呢?这就是我们接下来要说的ThreadPoolExecutor类了。

一、ThreadPoolExecutor

其实java中的四种线程池底层都是基于ThreadPoolExecutor进行实现的,只是具体的实现参数不同而已。我们先来看一下四种线程池的实例化源码。 1.单例线程池SingleThreadPool:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

2.缓存线程池newCachedThreadPool:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

3.定长线程池newFixedThreadPool:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

4.定时线程池newScheduledThreadPool:

   public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

从上面的实例化源码我们可以看出,这四种线程池都是基于ThreadPoolExecutor构建的,只是参数不同而已。那么ThreadPoolExecutor中的这些参数分别代表着什么含义呢?我们来看一下:

1.ThreadPoolExecutor中的各个参数含义

  • int corePoolSize: 线程池中的核心线程数

当有任务请求时,核心线程没有超过核心线程数,并且没有闲置线程时,会首先创建核心线程。默认情况下核心线程即使处于闲置状态也不会被销毁,可通过ThreadPoolExecutor的allowCoreThreadTimeOut属性进行设置。

  • int maximumPoolSize: 线程池中的线程总数

线程总数包含核心线程数和非核心线程数,非核心线程数=线程总数-核心线程数。只有当核心线程都不处于闲置状态,并且队列满了的情况下才会创建非核心线程,非核心线程在处于闲置状态一定时间后会被销毁。

  • **long keepAliveTime:**线程池中的非核心线程闲置超时时长

一个非核心线程,如果闲置状态的时长超过这个参数所设定的时长,就会被销毁掉,如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。

  • TimeUnit unit: 时间单位

TimeUnit是一个枚举类型,其包括: NANOSECONDS : 1微毫秒 = 1微秒 / 1000 MICROSECONDS : 1微秒 = 1毫秒 / 1000 MILLISECONDS : 1毫秒 = 1秒 /1000 SECONDS : 秒 MINUTES : 分 HOURS : 小时 DAYS : 天

  • BlockingQueue workQueue: 线程池中的任务队列

当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。这个是线程池中非常核心的概念,后面会详细介绍。

  • ThreadFactory threadFactory: 线程工厂,用来创建线程,可指定线程名称等信息

可通过new ThreadFactoryBuilder().setNameFormat("transfer-thread-pool-%d").build()来设置线程名称。

  • RejectedExecutionHandler handler: 可配置拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认使用策略)。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

2.ThreadPoolExecutor中的队列

上面我们提到了ThreadPoolExecutor中的队列,那么ThreadPoolExecutor中都有哪些队列呢?分别又有什么作用呢?我们来看一下: 线程池中的队列主要分为两种,无界队列有界队列。

无界队列指的是没有设置固定大小的队列。这些队列的特点是可以直接入列,直到溢出。当然现实几乎不会有到这么大的容量(超过 Integer.MAX_VALUE),所以从使用者的体验上,就相当于 “无界”。比如没有设定固定大小的 LinkedBlockingQueue。但是这样做有可能会使队列过大,甚至于造成内存溢出。

有界队列就是有固定大小的队列。可以直接使用java自带的有界队列,也可以通过setMaximumPoolSize方法将LinkedBlockingQueue等无界队列设置为有界队列。这样能够降低资源的消耗,但同时这种方式也使得线程池对线程的调度变得更困难,因为线程池和队列的容量都是有限的值,所以要想使线程池处理任务的吞吐率达到一个相对合理的范围,又想使线程调度相对简单,并且还要尽可能的降低线程池对资源的消耗,就需要合理的设置这两个数量。

那如何设置队列大小呢?以下给大家几个参考信息,具体还是要根据自己的需求情况来进行设置:

  • 如果要想降低系统资源的消耗(包括CPU的使用率,操作系统资源的消耗,上下文环境切换的开销等), 可以设置较大的队列容量和较小的线程池容量, 但这样也会降低线程处理任务的吞吐量。
  • 如果提交的任务经常发生阻塞,那么可以考虑通过调用 setMaximumPoolSize() 方法来重新设定线程池的容量。
  • 如果队列的容量设置的较小,通常需要将线程池的容量设置大一点,这样CPU的使用率会相对的高一些。但如果线程池的容量设置的过大,则在提交的任务数量太多的情况下,并发量会增加,那么线程之间的调度就是一个要考虑的问题,因为这样反而有可能降低处理任务的吞吐量。

3.ThreadPoolExecutor中常见的几种队列

  • SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!只有当线程池是无界的或者可以拒绝任务时,SynchronousQueue才有实际价值.

  • LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。可以选择设置初始队列大小,有界/无界队列,先进先出。

  • ArrayBlockingQueue:有界队列,可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误

  • DelayQueue:延迟的工作队列,无界队列,队列内元素必须实现Delayed接口,也就是说你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

  • PriorityBlockingQueue:优先级队列,有界队列,根据优先级来安排任务,任务的优先级是通过自然顺序或Comparator(如果任务实现了Comparator)来定义的。