线程池(thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,对线程统一管理。
使用线程池的优势
提高效率,创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的多。
减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间;
四种创建线程池的方式
Executors类(并发包)提供了4种创建线程池方法,这些方法最终都是通过配置ThreadPoolExecutor的不同参数,来达到不同的线程管理效果。
newCacheTreadPool
创建一个可以缓存的线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程,没回收的话就新建线程
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
//创建任务
Runnable runnable = new Runnable(){
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
};
newCachedThreadPool.execute(runnable);
}
newFixedThread
创建一个定长的线程池,可控制最大并发数,超出的线程进行队列等待。
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 5; i++) {
//创建任务
Runnable runnable = new Runnable(){
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
};
// 将任务交给线程池管理
newFixedThreadPool.execute(runnable);
}
newScheduleThreadPool
可以创建定长的、支持定时任务,周期任务执行。
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(2);
for (int i = 0; i < 5; i++) {
//创建任务
Runnable runnable = new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
// 将任务交给线程池管理,延迟2秒后才开始执行线程池中的所有任务
newScheduledThreadPool.schedule(runnable, 2, TimeUnit.SECONDS);
}
newSingleExecutor
创建一个单线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService newScheduledThreadPool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
//创建任务
Runnable runnable = new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
// 将任务交给线程池管理
newScheduledThreadPool.execute(runnable);
}
ThreadPoolExecutor 类介绍
ThreadPoolExecutor 是线程池最为核心的一个类,而线程池为它提供了四个构造方法,我们先来看一下其中最原始的一个构造方法,其余三个都是由它衍生而来
/**
* 用给定的初始参数创建一个新的ThreadPoolExecutor。
*/
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
int maximumPoolSize,//线程池的最大线程数
long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
可以看到这里有6个参数,这些参数直接影响到线程池的效果,以下是具体分析每个参数的意义
1、corePoolSize:线程池最小创建线程的数目,默认情况下,线程池中是没有线程的,也就是当没有任务来临的时候,初始化的线程池容量为0,而最小创建线程的数目则是在有线程来临的时候,直接创建 corePoolSize 个线程
2、maximumPoolSize:线程池能创建的最大线程的数量,在核心线程都被占用的时候,继续申请的任务会被搁置在等待队列里面,而当等待队列满了的时候,线程池就会把线程数量创建至 maximumPoolSize 个。
3、workQueue:核心线程被占有时,任务被搁置在任务队列
4、keepAliveTime:当线程池中的线程数量大于 corePoolSize 时这个参数就会生效,即当大于 corePoolSize 的线程在经过 keepAliveTime 仍然没有任务执行,则销毁线程
5、unit :参数keepAliveTime的时间单位
6、ThreadFactory:线程工厂:主要用来创建线程,一般默认即可
7、handler:饱和策略,即当线程池和等待队列都达到最大负荷量时,下一个任务来临时采取的策略
饱和策略的介绍: 即如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolTaskExecutor 定义一些策略:
ThreadPoolExecutor.AbortPolicy :抛出 RejectedExecutionException来拒绝新任务的处理。
ThreadPoolExecutor.CallerRunsPolicy :调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
ThreadPoolExecutor.DiscardPolicy :不处理新任务,直接丢弃掉。
ThreadPoolExecutor.DiscardOldestPolicy : 此策略将丢弃最早的未处理的任务请求。
ThreadPoolExecutor
的状态有5
种,分别为:
RUNNING
,表示可接受新任务,且可执行队列中的任务;SHUTDOWN
,表示不接受新任务,但可执行队列中的任务;STOP
,表示不接受新任务,且不再执行队列中的任务,且中断正在执行的任务;TIDYING
,所有任务已经中止,且工作线程数量为0
,最后变迁到这个状态的线程将要执行terminated()
钩子方法,只会有一个线程执行这个方法;TERMINATED
,中止状态,已经执行完terminated()
钩子方法;
线程池处理任务的策略
- 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
- 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
- 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
- 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
workQueue任务队列
workQueue任务队列一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列;
1、直接提交队列synchronousQueue: 这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务
2、有界任务队列ArrayBlockingQueue: 基于数组的先进先出队列,此队列创建时必须指定大小;
3、无界任务队列LinkedBlockingQueue: 基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
4、优先任务队列PriorityBlockingQueue: 优先任务队列通过PriorityBlockingQueue实现