一、前言
这篇文章记录下Java中常见的几种线程池,线程池可以通过Executors
去创建,几个常见的生成线程池的方法如下:
newCacheThreadPool
newFixedThreadPool
newScheduledThreadPool
newSingleThreadExecutor
newSingleThreadScheduledExecutor
在了解线程池之前,我们需要先来了解创建线程池的几个重要的参数:
-
corepollsize:核心池的大小,默认情况下,在创建线程池后,每当有新的任务来的时候,如果此时线程池中的线程数小于核心线程数,就会去创建一个线程执行(就算有空线程也不复用),当创建的线程数达到核心线程数之后,再有任务进来就会放入任务缓存队列中。当任务缓存队列也满了的时候,就会继续创建线程,知道达到最大线程数。如果达到最大线程数之后再有任务过来,那么就会采取拒绝服务策略。
-
maximumpoolsize:线程池中最多可以创建的线程数
-
keeplivetime: 线程空闲状态时,最多保持多久的时间会终止。默认情况下,当线程池中的线程数大于corepollsize 时,才会起作用 ,直到线程数不大于 corepollsize ;
-
TimeUnit:线程活动保持时间的单位:可选的单位有天(DAYS)、小时(HOURS)等;
-
workQuque: 阻塞队列,用来存放等待的任务,有以下几种队列:
(1)ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO对元素进行排序;
(2)LinkedBlockingQueue:基于链表结构,同样遵循FIFO,吞吐量要高于ArrayBlockingQueue;
(3)SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于BlockingQueue;
(4)PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
-
rejectedExecutionHandler:饱和策略,即当队列和线程池都满了的时候,需要采取一种呢能够策略处理提交的新任务,有以下四种策略:
(1)abortpolicy:丢弃任务,抛出异常
(2)discardpolicy:拒绝执行,不抛异常
(3)discardoldestpolicy:丢弃任务缓存队列中最老的任务
(4)CallerRunsPolicy:线程池不执行这个任务,主线程自己执行。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// ... do sth
}
二、FixedThreadPool
1. 应用举例
ExecutorService threadPool = Executors.newFixedThreadPool(3);
for(int i = 0; i<10; i++){
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
2. 详细说明
FixedThreadPool 被称为可重用固定线程数的线程池,下面是 FixedThreadPool 的源代码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
三、CacheThreadPool
CacheThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。可以通过newCacheThreadPool
创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回空闲线程,若无可回收线程,则新建线程。
这种类型的线程池的特点是:
- 工作线程的创建基本没有限制(如果
Integer.MAX_VALUE
也算限制的话),这样可以比较灵活地往线程池中添加线程; - 如果长时间没有往线程池中提交任务,那么空闲的线程将会被自动终止(默认等待1分钟);
下面是CacheThreadPool的源代码实现:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
四、SingleThreadExecutor
SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。下面是SingleThreadExecutor的源代码实现:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
五、ScheduleThreadPool
ScheduleThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来给定的延迟之后运行任务,或者定期执行任务。ScheduleThreadPoolExecutor的功能与Timer类似,但ScheduleThreadPoolExecutor功能更加强大、更灵活。Timer对应的是单个后台进程,而ScheduleThreadPoolExecutor可以在构造函数中指定多个对应的后台线程函数。
Executors.newScheduledThreadPool(3).scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
System.out.println("ScheduledThreadPool "+Thread.currentThread().getName());
}
}, 3, 1, TimeUnit.SECONDS
);
六、注意的点
-
线程池必须通过线程池提供,不允许在应用中自行显式创建线程;
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存后者“过度切换”的问题。
-
线程池不允许使用Executors去创建,而是通过
ThreadPoolExecutor
的方式,这样的处理方式可以让开发人员更加明确线程池的运行规则,规避资源耗尽风险。说明:Executors返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;
2) CacheThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM;