为什么要使用线程池?

首先我们想一想为什么我们要使用线程池呢?这里主要我们可以给出以下原因:

  1. 如果创建线程的频率很高,每次处理的时间会很短,这时我们频繁的创建和销毁线程会造成巨大的开销。
  2. 其次如果每次需要一个线程就创建一个线程,我们管理线程将会很不方便。

上面就是我们在开发时需要考虑的是否要使用线程池的因素。

Java中提供的线程池

Java中可以使用Executors提供的静态工厂方法创建了四种线程池:

Executors.newCachedThreadPool()  创建一个无大小限制的线程池,只要需要创建新
的线程则立即创建。每个线程如果在60秒内没有处理的则会销毁。
Executors.newFixedThreadPool()  创建一个固定大小的线程池。
Executors.newScheduledThreadPool()  创建一个延期执行的线程池。
Executors.newSingleThreadExecutor()  创建一个只有一个线程的线程池。 

这四个静态工厂方法的实现如下:

newCachedThreadPool()

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

newFixedThreadPool()

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

newScheduledThreadPool()

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

new ScheduledThreadPoolExecutor(corePoolSize)的实现如下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
    

newSingleThreadExecutor()

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

从上面的实现我们可以发现

  1. newCachedThreadPool()和newFixedThreadPool()直接创建了一个ThreadPoolExecutor对象,只是参数不同。
  2. 而newSingleThreadExecutor()则是新建一个ThreadPoolExecutor对象并作为参数传递给FinalizableDelegatedExecutorService,FinalizableDelegatedExecutorService是作为一个代理类,最终的操作都是通过ThreadPoolExecutor来执行,你可以参考关于SingleThreadExecutor以及FinalizableDelegatedExecutorService这篇文章理解FinalizableDelegatedExecutorService的作用。
  3. newScheduledThreadPool()则是创建一个ScheduledThreadPoolExecutor对象,ScheduledThreadPoolExecutor继承ThreadPoolExecutor并实现了接口ScheduledExecutorService。ScheduledExecutorService接口定义了提交延迟反诬的四个方法。下面是ScheduledThreadPoolExecutor的继承结构图:

图片描述

通过上面的分析我们明白了Java中四种线程池的核心都是ThreadPoolExecutor。下面来看一下ThreadPoolExecutor()的构造函数:

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

参数的意义:
corePoolSize:线程池中线程的数量,这些线程就算处于空闲状态也不会回收
maximumPoolSize:线程池中允许的线程的最大数量
keepAliveTime:超过corePoolSize数量的线程的最大存活时间
unit:KeepAliveTime的时间单位
workQueue:用于存放execute方法提交的任务
factory:用于创建线程的工程,通常可以设置线程的名字等一些属性方便调试
handler:当线程池已经关闭或者线程数量已经饱和且任务队列也饱和了

观察上面的各项参数并且结合静态工厂方法的实现,可以确定各种线程池最本质的区别就在于这几项参数值得不同。

线程池的执行流程

除了ScheduledThreadPoolExecutor之外,另外的三种线程池的submit方法的执行逻辑都是一样的,如下图:
图片描述

执行逻辑简单描述如下:

  1. 执行submit方法提交任务。
  2. 首先检查当前线程数是否小于corePoolSize,小于则添加新的worker并将任务交给新建的worker运行。
  3. 将任务添加到任务队列中。
  4. worker会循环获取任务并执行。

ScheduledThreadPoolExecutor不同的是先添加任务,然后根据当前线程数量的大小决定是否创建新的Worker。

需要说明的是,Worker是一个实现了Runnable接口的类,每次创建新的worker都是创建一个Thread对象并将worker对象作为参数传递给Thread对象。之后调用Thread对象的start方法会执行Worker的run方法。Worker的run方法会执行Task的run方法。

拒绝策略

线程池中总共涉及到四种拒绝策略,它们都定义在ThreadPoolExecutor中,分别是:

CallerRunsPolicy  在当前线程直接执行任务,即直接调用task.run()

AbortPolicy  抛出运行时异常

DiscardPolicy 放弃当前任务,不做任何事

DiscardOldestPolicy  移除队首的任务,并且重新执行任务

当出现线程池已经关闭或者线程池和任务队列已经饱满时,最终都会调用到这些拒绝策略的rejectedExecution方法。

阻塞队列

Java线程池总共涉及到了三种阻塞队列,它们分别是SynchronousQueue,LinkedBlockingQueue,DelayedWorkQueue。下面简单介绍一下这几种队列的特点。

SynchronousQueue:这是一个不存储任何元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。

LinkedBlockingQueue:这是一个有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。

DelayedWorkQueue:DelayedWorkQueue是ScheduledThreadPoolExecutor的内部类。
它使用数组实现存放任务,是没有上届的,可以不断放入数据,并且它使用延迟时间进行排序,
延迟时间少得排在前面。

DelayedWorkQueue可以参考Java优先级队列DelayedWorkQueue原理分析

未完待续


水一水
39 声望5 粉丝

总结经验,提升自己