预定义线程池
前面的章节中我们介绍了“池化”的思想和基本结构,以及 Java中对线程池操作的抽象概念,并没有介绍线程池的实现。这个小节中,我们会介绍 JUC 的 Executors 类中预定义的一些线程池实现。
ThreadPoolExecutor
正确实现线程池的难度是非常大的,Java 中的线程池通常是基于 ThreadPoolExecutor
构建的,要么通过构造函数来改变行为,要么通过继承它来扩展行为。我们先来看看它的构造函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// ...
}
我们看到,多数概念跟我们之前提到的“池化”的基本结构是对应的:
各个参数也可以在图中找到直接的对应关系:
corePoolSize,线程池中线程的保底数量,新来任务时会创建线程,直到达到corePoolSize数量maximumPoolSize,队列满时,会创建新线程,该参数决定允许创建的最大的线程数keepAliveTime,多出保底线程数的线程不用时需要被销毁,该参数表示最大空闲等待时间unit,等待时间的单位workQueue,提交任务的等待队列threadFactory,创建新线程时使用的工厂实例,可定制名称,UncaughtExceptionHandler等。handler,workQueue 满时,会拒绝新的任务,handler用来决定如何拒绝。
Executors 工厂类
那么 Executors 中提供了哪些预设的线程池呢?
-
newFixedThreadPool,创建固定线程数的线程池,新任务提交时,线程池会不断创建新的线程,直到达到设定的最大值,其后不创建也不销毁线程,如果线程意外退出,会再创建新线程直到最大值。public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } -
newSingleThreadExecutor,创建只有单个线程的线程池,所以实际上提交到这个线程池的任务并不会并发运行。public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } -
newCachedThreadPool,创建可缓存的线程池,当任务多于当前线程,则会创建新的线程用于执行任务,当线程空闲时会被回收,创建的线程数不受限制。public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
除此之外,Executors 还提供一些基于其它线程池实现的预设线程池:
newScheduledThreadPool,创建固定大小的线程池,提交的任务可以指定以延迟或定时的方式运行,严格来说它返回的是ScheduledExecutorService,虽然这个接口也继承了ExecutorServicenewSingleThreadScheduledExecutor与newScheduledThreadPool类似,只是线程池中只有单个线程。newWorkStealingPool构造工作窃取队列,是 JDK 1.8 新增的方法,基于 JDK 1.7 增加的ForkJoinPool实现,内部会使用多个工作队列来降低对队列的竞争,队列大小无上限。
不过,注意到 Executors 创建的线程池使用的都是无界的队列,会有潜在过载导致
OOM 的问题,通常建议使用有界队列(但是最好搞清楚它们的差异)。另外,如果使用
Spring 框架,建议使用它的 TaskExecutor 抽象,使用 TaskExecutorBuilder 创建线程池,更方便定制。
小结
本节主要介绍了 ThreadPoolExecutor 的基本结构和参数,并介绍了 Executors 提供的一些预定义的方法。一方面可以直接使用,另一方面也方便了解一些常用的线程池需求如何实现。
当然,这里只是介绍如何创建,但是创建时要用什么参数就是门学问了,下节会介绍一些常见的注意事项,更多时候还是需要掌握基本原理,具体问题具体分析。