什么是线程池?
线程池通俗的讲就是在程序启动时创建好若干个线程,供程序去调度和使用,当程序需要使用线程时不再需要去频繁的创建和销毁线程,而可以去线程池中获取空闲的线程直接使用,当使用完后线程进入空闲状态并非直接销毁线程。从JDK1.5开始,JAVA API的java.util.concurrent包下提供了Executors供开发人员方便的进行不同类型线程池的创建。
为什么使用线程池?
- 减小线程频繁创建和销毁对系统资源的消耗
- 提高系统响应速度,当系统需要使用线程时直接从池中获取
- 线程池可以根据实际情况进行调整,有效控制最大并发数
Executors创建常见的几种线程池
- 创建固定大小的线程池:指定线程池核心线程数和最大线程数
ExecutorService executorService = Executors.newFixedThreadPool(8);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 创建单个线程的线程池:仅有单线程执行任务,并按顺序执行
ExecutorService executorService = Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 可缓存的线程池:线程数无限制,有空闲线程则复用,无空闲线程则创建。
ExecutorService executorService = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 定时线程池:可周期性或延时执行任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(8);
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//延时执行(延迟10s执行dotask)
scheduledExecutorService.schedule(doTask(),10, TimeUnit.SECONDS);
//延迟10s开始执行dotask,每1s执行一次,下次执行时间=上次任务开始时间+时间间隔,任务执行时间不算在两次间隔时间内
scheduledExecutorService.scheduleAtFixedRate(doTask(),10,1,TimeUnit.SECONDS);
//延迟10s开始执行dotask,每1s执行一次,下次执行时间=上次任务执行结束时间+时间间隔,任务执行时间计算在两次间隔时间内
scheduledExecutorService.scheduleWithFixedDelay(doTask(),10,1,TimeUnit.SECONDS);
自定义线程池
虽然Executors工具类为我们提供了许多创建线程池的方法,但是都不推荐使用,推荐使用ThreadPoolExecutor进行线程的创建,如下为该类的几种构造方法:
//全部参数都自定义(共7个参数)
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){
...
}
//使用默认的线程创建工厂(6个参数)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
//使用默认的任务拒绝策略(6个参数)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
//使用默认的线程创建工厂和默认的拒绝策略(5个参数)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
不难发现,Executors创建线程池就是使用ThreadPoolExecutor进行创建的,只是不同的线程池使用了不同的固定参数,ThreadPoolExecutor类的全参构造共有七个参数,接下来对这些参数进行说明。
线程池创建时的参数
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){
...
}
- 核心线程数corePoolSize
线程池的基本大小,当allowCoreThreadTimeOut=false(默认)时,线程池的线程数永远大于等于核心线程数(当线程已被创建的情况),即使当前没有任务要执行核心线程也不会被销毁。可以理解为线程池中的常驻线程就是核心线程,它永远不会被销毁。当核心线程全部处于运行状态,并且阻塞队列中等待的任务已满,线程池就会创建新的线程,此时创建的线程就是非核心线程,但核心线程+非核心线程的数量不能大于最大线程数。 - 最大线程数maximumPoolSize
线程池中可存活的最大线程数量,核心线程数+非核心线程数<=最大线程数 - 线程(空闲)可存活时间keepAliveTime
当线程池中的非核心线程在keepAliveTime的时间后依旧处于空闲状态,那么该线程就会被销毁。当allowCoreThreadTimeOut=true时,核心线程也会被销毁。 - 存活时间的单位unit
keepAliveTime的单位,有如下单位:
TimeUnit.NANOSECONDS 纳秒
TimeUnit.MICROSECONDS 微秒
TimeUnit.MILLISECONDS 毫秒
TimeUnit.SECONDS 分钟
TimeUnit.MINUTES 小时
TimeUnit.HOURS 小时
TimeUnit.DAYS 天 - 任务队列workQueue
用于存储当线程全部被占用时等待执行的任务的阻塞队列。 - 线程工厂threadFactory
指定线程池如何创建线程 - 拒绝策略handler
当线程池线程数达到最大值,且阻塞队列的任务已满,指定对于新的任务该如何处理。
ThreadPoolExecutor类的其他成员变量
- private volatile boolean allowCoreThreadTimeOut;
核心线程是否有超时时间,如果为true,则空闲时间到达超时时间后销毁核心线程。 - private final HashSet workers = new HashSet();
用于存储当前线程池的线程,一个Worker即为一个线程。 - private int largestPoolSize;
线程池存活线程的历史最大值 - private long completedTaskCount;
线程池总共完成的任务总数 - private volatile int corePoolSize;