引言
在阿里巴巴Java开放手册中有这么一条:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
如果是不清楚Java线程池基本结构的同学看到这条强制约定可能会感到云里雾里。而本文将通过介绍Java线程池的基本使用与结构,让同学们能够抓住其中的来龙去脉。
概述
首先我们要明确的是,线程的创建和销毁是有代价的,因此往往我们要引入池化技术,也就是所说的线程池,它的特点有:
- 重用线程池中的线程,减少因对象创建,销毁所带来的性能开销;
- 能有效的控制线程的最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞;
- 能够多线程进行简单的管理,使线程的使用简单、高效。
注意本文主要探讨的是Jav线程池的基本使用,如果想了解基本原理的可以移步啊啊。
基本结构
java中的线程池是通过Executor框架实现的,Executor 框架包括类:Executor,Executors,ExecutorService,ThreadPoolExecutor ,Callable和Future、FutureTask的使用等。
如下图:
Executor: 所有线程池的接口,只有一个方法,就是执行命令。
public interface Executor {
void execute(Runnable command);
}
ExecutorService: 增加Executor的行为,是Executor实现类的最直接接口。
ThreadPoolExecutor:线程池的具体实现类,一般用的各种线程池都是基于这个类实现的。
构造方法如下:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
构造方法参数说明:
- corePoolSize:核心线程数。默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。
- maximumPoolSize:线程池所能容纳的最大线程数
- keepAliveTime:非核心线程的闲置超时时间,超过这个时间就会被回收。
- unit:指定keepAliveTime的单位,如TimeUnit.SECONDS。
- workQueue: 线程池中用于存放任务的队列.
- threadFactory:线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个newThread方法,通过线程工厂可以对线程的一些属性进行定制。.
- RejectedExecutionHandler:当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略.默认策略是丢弃任务并抛出RejectedExecutionException异常。
通常有四种拒绝策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
Executors: 提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService 接口。
线程池的工作流程
-
线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
-
当调用 execute() 方法添加一个任务时,线程池会做如下判断:
-
如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务; -
如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会采取拒绝策略。
-
当一个线程完成任务时,它会从队列中取下一个任务来执行。
6.当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
工具类Executors创建线程池
现在我们在来分析下阿里巴巴提到的几个创建线程的方法
newFixedThreadPool:有核心线程的线程池,大小固定 (其缓冲队列是无界的) 。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
SingleThreadExecutor:单个后台线程 (其缓冲队列是无界的)。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看到以上的两种线程池都是使用LinkedBlockingQueue作为任务队列,而这种队列是无界的,因此阿里巴巴Java开发手册才会解释说:
FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
让我们继续看另外两种。
CachedThreadPool:无界线程池,可以进行自动线程回收。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ScheduledThreadPool:核心线程池固定,大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
public static ExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPool(corePoolSize,
Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
可以看到这两种线程池指定的maximumPoolSize都是Integer.MAX_VALUE,因此阿里巴巴Java开发手册才会解释说:
CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。