为什么要有线程池?
如果并发线程数量很多,且每个线程执行任务时间很短,就会频繁创建和销毁线程,这种开销会大大降低系统的效率。为了线程的复用有了线程池这一概念。
1.线程池的创建
工具类Executors里提供了多种创建线程池的静态方法:
public class Executors {
...
//创建一个固定大小的线程池。适用于可以预测线程数量的业务中,或者服务器负载较重,对当前线程数量进行限制。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(...);
}
//可以扩大的线程池,最大值为Integer.MAX_VALUE。适用于服务器负载较轻,执行很多异步任务。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(...);
}
//单线程线程池。适用于需要保证顺序执行各个任务,并且在任意时间点,不会有多个线程活动的场景。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(...));
}
//可以延时启动,定时启动的线程池。适用于需要多个后台线程执行周期任务的场景。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//拥有多个任务队列的线程池。创建当前可用cpu数量的线程来并行执行,适用于大耗时操作并行执行
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool(...);
}
...
}
2.线程池核心类ThreadPoolExecutor
源码中可以看出线程池有个核心类java.util.concurrent.ThreadPoolExecutor,谱系关系如下:
public class ThreadPoolExecutor extends AbstractExecutorService {}
public abstract class AbstractExecutorService implements ExecutorService {}
public interface ExecutorService extends Executor {}
public interface Executor {
void execute(Runnable command);
}
Executor为最顶层的接口,只声明了一个用来执行任务的方法,ThreadPoolExecutor才是接口的实现类。下面来看ThreadPoolExecutor的部分源码:
public class ThreadPoolExecutor extends AbstractExecutorService {
...
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {
...
}
}
3.线程池参数
可以看出ThreadPoolExecutor有四个构造方法,前三个都是调用的最后一个。而最后一个构造方法有七个参数:
int corePoolSize; //核心线程数
int maximumPoolSize; //最大线程数
long keepAliveTime; //线程没有任务执行时保活时间。默认情况下,只有当池中的线程数 > corePoolSize时,此参数才会起作用,直到线程数 <= corePoolSize
TimeUnit unit; //keepAliveTime的时间单位
BlockingQueue<Runnable> workQueue; //任务队列。用来存储已被提交但尚未执行的任务,这里的任务是由ThreadPoolExecutor的execute方法提交的。
ThreadFactory threadFactory; //线程工厂。为线程池创建新线程
RejectedExecutionHandler handler; //任务拒绝策略。当前线程数已经达到maximumPoolSize而且任务队列已满,线程池会拒绝处理新任务。
4.线程池状态
private static final int RUNNING ;//创建线程池后,线程池处于running状态
private static final int SHUTDOWN ;//调用shutdown(),则RUNNING -> SHUTDOWN,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
private static final int STOP ;//调用shutdownNow(),则(RUNNING or SHUTDOWN ) -> STOP,此时线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务;
private static final int TIDYING ;//当线程池在SHUTDOWN状态下,所有的任务已终止,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING;当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
private static final int TERMINATED ;//线程池处在TIDYING状态下,执行完terminated()之后,线程池彻底终止,就会由 TIDYING -> TERMINATED。
5.线程池几个重要方法
execute()
submit()
shutdown()
shutdownNow()
- execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过它可以向线程池提交一个任务,由线程池去执行。返回类型void。
- submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。
6.任务缓存队列及排队策略
workQueue用来存放等待执行的任务,类型为BlockingQueue<Runnable>,通常可以取下面三种类型:
ArrayBlockingQueue //基于数组的先进先出队列,此队列创建时必须指定大小;
LinkedBlockingQueue //基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
synchronousQueue //这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
7.线程池拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy;//丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy;//也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy;//丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy;//由调用线程处理该任务