JDK的线程池相信大家都有用过,它预先创建好一部分线程,使用完后放回池中,避免了创建与销毁线程的昂贵开销,使得性能大大提升,和数据库连接池是一样的道理,本文简单介绍一下java中的几种线程池。
jdk提供了Executors类方便我们创建线程池,我们平时都是使用它的静态方法newxxxxThreadPool进行线程池的创建。1.8中一共提供了5种线程池。
1.固定数量的线程池
这个比较好理解,即线程的数量是固定的,线程并不会随着任务的多少而变化。可以发现他最终是通过实例化ThreadPoolExecutor来实现。ThreadPoolExecutor的构造函数有很多参数,每个参数代表的含义是需要了解的,他决定了ThreadPoolExecutor的行为,我们来看一下他的参数最多的构造函数。
参数最多构造函数 后俩个参数为默认值的构造函数 默认拒绝策略corePoolSize:线程池的核心线程数量
maximumPoolSize:线程池中允许的最大线程数
keepAliveTime:当池中线程数大于核心线程数时,该时间为余下线程(存活线程总数-核心线程数)的最大空闲存活时间
unit:时间单位
workQueue:工作队列,存放将要执行的任务的地方
threadFactory:创建线程的线程工厂
handler:达到线程上限或者队列容量上限时执行的拒绝策略。
这里需要说明一下线程池的执行行为。当核心线程未满时新来任务会创建核心线程,当corePoolSize满了以后,任务会进入阻塞队列,只有当阻塞队列满了以后,才会创建非核心线程,当最大线程数与队列均满了以后,才会执行拒绝策略。所以这里就有一个问题,假如你希望你配置的maximumPoolSize能够生效,那么workQueue一定不可以是无界队列(关于队列部分会再写一篇文章)。
我们接着分析FixedThreadPool,通过构造函数我们可以发现,corePoolSize=maximumPoolSize,工作队列为LinkedBlockingQueue并且没有指定容量,即最大容量为Integer.MAX_VALUE。使用的线程工厂为默认,使用的拒绝策略也为默认。拒绝策略这里也顺便说一下,jdk提供了4种拒绝策略。
AbortPolicy:这个比较简单粗暴,直接抛出异常,也是默认的拒绝策略。
CallerRunsPolicy:直接在调用线程中执行该任务。
DiscardPolicy:直接将该任务丢弃。
DiscardOldestPolicy:丢弃最老的未被执行的任务,并重复入队操作
2.并行线程池
这个是1.8中新加入的线程池,可以看到主要使用了ForkJoin相关的操作,ForkJoin本人没有使用过,也比较复杂,这里就不说了(因为不会)。
3.只有一个线程的线程池
其实这个可以不称为线程池,因为只有一个线程。核心线程=最大线程=1,这个比较适合需要保证队列中任务顺序执行的场景。
4.缓存线程池
他根据需要创建线程,没有核心线程,当60s内没有任务时,将会回收存活的线程,60s内有任务时,他可以重用已有的线程。注意他的工作队列是SynchronousQueue,这里简单的说一下),他的每一个put操作必须等待take操作,这意味着如果任务生产速度大于消费速度,那么他将不会创建新线程。该线程池适合执行大量小任务的场景。
5.延时线程池
ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,所以super最终会调到ThreadPoolExecutor的构造函数,可以看到,最大线程数为int最大值,工作队列为延时队列DelayedWorkQueue,该线程池适合执行延时任务。
网友评论