本文学习过程中持续更新
线程池的创建
为什么不推荐用 Executors
创建线程池,而是用 ThreadPoolExecutor
的方式?
通过 Executors
创建,以 FixedThreadPool
举例
ExecutorService exec = Executors.newFixedThreadPool(thread_num)
public class Executors {
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
}
会创建一个 ThreadPoolExecutor
在这里的构造方法里,传5个参数,参数如下:
- corePoolSize: 除非设置了
允许核心线程超时的最大时间
,否则即使线程处于空闲状态,也要保留在池中的线程数 - maximumPoolSize: 线程池维护线程的最大数量
- keepAliveTime: 当线程数大于核心时,这是多余空闲线程在终止之前等待新任务的最长时间
- unit: 上述时间的单位
- workQueue: 在执行任务之前用于保存任务的缓冲队列,此队列仅包含
excute
方法提交的Runnable
任务
当然,这只是 ThreadPoolExecutor
的构造方法之一,ThreadPoolExecutor
还有更多参数的构造方法,这里先不谈。
再来到 excute
方法,该方法用于执行提交的 Runnable
任务。
excute
的执行逻辑大概如下:
- 当池中线程数量少于
corePoolSize
,创建一个新线程去执行任务 - 当池中线程数量等于
corePoolSize
,如果缓冲队列workQueue
未满,放入缓冲队列 - 如果缓冲队列满,线程池数量小于
maximumPoolSize
, 建新的线程来处理任务 - 如果创建新的线程来处理任务失败,说明线程池数量饱和,即达到了
maximumPoolSize
,会拒绝这次任务
在 ThreadPoolExecutor
中,还有一个 RejectedExecutionHandler
用来对拒绝的任务进行处理,这里先不谈。
从 excute
的执行方法中可以看到,缓冲队列也有个饱和值,这个值是可以由我们自己设置的。
举个例子,直接使用 ThreadPoolExecutor
创建的时候:
int count = 100;
ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(count));
而上文中,用 Executors
创建 FixedThreadPool
:
ExecutorService exec = Executors.newFixedThreadPool(thread_num)
public class Executors {
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
}
这里的缓冲队列初始化方法是这样的:
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
允许缓冲队列的长度为 Integer.MAX_VALUE
,这时可能会堆积大量的请求,有OOM风险。
创建 SingleThreadExecutor
与之同理,存在缓冲队列堆积大量请求风险。
用 Executors
创建 CachedThreadPool
:
ExecutorService exec = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
允许创建的线程数量为 Integer.MAX_VALUE
,会堆积大量的线程,有OOM风险。
创建 ScheduledThreadPool
与之同理,有堆积大量线程的风险。
所以推荐用 ThreadPoolExecutor
的方式创建线程池,手动设置各种参数(包括最大线程数、缓冲队列的最大长度)。