为什么要使用线程池?
创建线程是简单的,但启动后的线程犹如脱缰野马,难于管理,特别是多线程使用场景,线程之间的互相竞争,可能使 cpu 花费更多时间在各个线程之间切换,而且线程结束后的回收由垃圾回收控制,你不知道工作结束的线程还会存活多久,是否持有着什么资源。而且线程对象提供使用方法有限,无法提供定时启动、线程并发数控制等操作,所以,线程池出现了。
线程池提供了对线程的管理,让线程稍微能得到控制(中断、取消等);并发线程数的控制,提高系统资源使用率,避免过多的资源竞争;复用线程,减少线程对象创建,提高线程启动效率以及降低系统资源消耗;提供延时启动、定期执行等操作。
线程池介绍
具体线程池有以下几个:
ThreadPoolExecutor
继承AbstractExecutorService
,Executors
提供的默认线程池,除了定时线程池,其他都是ThreadPoolExecutor
的默认参数构建ScheduledThreadPoolExecutor
可调度线程池(定时线程池)
继承ThreadPoolExecutor
实现ScheduledExecutorService
ForkJoinPool
,继承AbstractExecutorService
特殊线程池,分治法(Divide-and-Conquer Algorithm),可以将一个大任务(线程)在细分多个小任务执行,直到所有小任务完成才算大任务完成,充分利用多核 cpu 的特性。与ThreadPoolExecutor
区别,ForkJoinPool
可以实现在有限的线程数下完成非常多的任务,如使用4个线程完成具有父子关系的400万个任务,而ThreadPoolExecutor
无法选择优先执行子任务,所以处理400万的任务,需要400万个线程。ExecutorCompletionService
实现CompletionService
,构造方法传入Executor
,可以认为是对Executor
的使用封装。
使用较多的线程池是 ThreadPoolExecutor
,本文只介绍这个。对其他类型有兴趣的可以去 java.util.concurrent
中查看。
构造方法:
public ThreadPoolExecutor(int corePoolSize,//核心池数量,并发的线程数
int maximumPoolSize,//线程池最大容量
long keepAliveTime,//线程存活时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//线程缓存队列
ThreadFactory threadFactory,//线程创建工厂
RejectedExecutionHandler handler//加入线程失败策略) {
//... 省略
}
向线程池提交一个任务,如果核心池未满,则用该命令作为第一个任务开启一个新线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))//true:添加成功 false:添加失败
return;
c = ctl.get();
}
如果核心池已经满了,检查线程池最大容量,没有超过,则加入缓存工作队列,创建新线程去执行任务;当线程池达到最大容量,并且缓存队列已经饱和时,此时再继续往线程池中添加任务,则会拒绝。
由缓存队列创建的线程,如果处于空闲状态,在一定的时间(keepAliveTime)后将会被回收,而核心池中的线程如果 allowCoreThreadTimeOut
该参数为 false
(默认值) 即使处于空闲状态也不会被回收。
流程图如下:
使用:
ThreadPoolExecutor pool = new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
pool.execute(()-> print "runnable");
一般来说,除非有特殊的处理要求,否则建议使用Executors
中的方法构造线程池,对于线程池的核心数、最大容量、工作队列结构、线程工厂等设置,默认构造基本满足开发需求。
关闭线程池:
//不再接受新任务,直到缓存区任务执行完毕
public void shutdown(){...}
//不再接受新任务,将缓存区任务返回,并清空工作区,中断运行中的任务
public List<Runnable> shutdownNow() {...}