线程池介绍

概况

线程池,从字面含义来看,是指管理一组同构工作线程的资源池。线程池是与工作队列密切相关的,其中在工作队列中保存了所有等待执行的任务。工作者线程的任务很简单:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。在上一节当中,我们介绍了同步容器及并发容器相关的知识,在最后,我们讲到了阻塞队列这类并发容器。线程池的实现中,工作队列就用到了阻塞队列。
类库提供了一个灵活的线程池以及一些有用的默认配置,可以通过调用Executors中的静态工行方法之一来创建一个线程池:线程池创建
从函数名字可以看出,Executors能够创建一下几种类型的线程池:

  • newFixedThreadPool
    创建一个固定长度的线程池,每当提交一个任务时就创建一个线程成,知道达到线程池的最大数量,这是线程池的规模将不再变化
  • newSingleThreadExecutor
    创建一个单线程的线程池。
  • newCachedThreadPool
    创建一个可缓存的线程池,如果线程池当前规模超出了处理器需求,那么将回收空闲的线程,而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。
  • ScheduledExecutorService
    创建一个固定长度的线程池,而且以延迟或者定时的方式来执行任务。

在这几个静态构造函数中,其实实质上都调用了ThreadPoolExecutor类的构造函数来创建一个线程池。我们来看下,包java.util.concurrent下ThreadPoolExecutor类的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

在该构造函数当中,corePoolSize表示线程池中线程的数量,maximumPoolSize表示线程池中最大线程数据,keepAliveTime表示的是,当线程池中的线程数量大于corePoolSize时,闲置线程终结前等待任务分配的最大等待时间(如果超过这个时间线程还没有被分配任务,该线程将终结)。workQueue,handler,threadFactory是线程池的核心内容,我们将具体来讲解一下这三个属性的作用。

工作队列(BlockingQueue

首先,来看一下该属性的定义:

/**
 * The queue used for holding tasks and handing off to worker
 * threads.  We do not require that workQueue.poll() returning
 * null necessarily means that workQueue.isEmpty(), so rely
 * solely on isEmpty to see if the queue is empty (which we must
 * do for example when deciding whether to transition from
 * SHUTDOWN to TIDYING).  This accommodates special-purpose
 * queues such as DelayQueues for which poll() is allowed to
 * return null even if it may later return non-null when delays
 * expire.
 */
private final BlockingQueue<Runnable> workQueue;

BlockingQueue就是在上一篇文中当中介绍到的阻塞队列。具体的实现类,则需要根据具体的业务场景以及其他的配置参数做出选择。从同步容器与并发容器类简介中我们可以知道,阻塞队列可粗分为三类,无界队列,有界队列,以及同步移交(Synchronous Handoff)。对于无界队列,不存在队列的饱和情况,而对于游街队列及同步移交来说,当任务的数量大于固定队列的大小时,如果处理这部分对于的任务,则需要根据饱和策略来决定。

饱和策略(RejectedExecutionHandler)

当有界队列被填满后,饱和策略开始发挥作用。JDK提供了几种不同的RejectedExecutionHandler实现,每种实现都包含了不同的饱和策略:

  • AbortPolicy
    “中止”策略是默认的饱和策略。该策略将抛出未检查的RejectedExecutionException异常。
  • CallerRunsPolicy
    “调用者运行”策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。它不会在线程池的某个线程中执行新提交的任务,而是在一个调用了excute的线程中执行该任务。
  • DiscardPolicy
    当新提交的任务无法保存到队列中等待执行时,“抛弃(Discard)”策略会悄悄抛弃该任务。
  • DiscardOldestPolicy
    “抛弃最旧的”策略会抛弃下一个将被执行的任务。

线程工厂(ThreadFactory)

每当线程池需要创建一个线程时,都是通过线程工厂方法来完成了。默认的线程工厂将创建一个新的,非守护的线程,并不包含特殊的配置信息。因此,当我没需要创建特殊的线程(不如在创建过程当中打印日志,给线程修改名字)时,就可以提供我们自己的线程工厂实例就可以了。自定义一个线程工厂很简单,只需要实现ThreadFactory接口即可:

1
2
3
4
5
6
7
8
9
10
11
12
public interface ThreadFactory {

/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}

该接口只有一个函数,我们可以通过该函数,定制化我们自己的线程。
线程池的介绍就到这里,在程序代码中,强烈建议,不要显示地定义线程去执行任务,而是通过线程池来执行任务,防止不可预料的错误发生。

欢迎关注个人公众号:
个人公号