什么是ThreadPoolExecutor?
JDK1.5开始出现,继承关系如下:
ThreadPoolExecutor <- AbstractExecutorService <-ExecutorService <- Executor
后面两个是接口,官方注释表明主要是解决两个问题:
1.当执行大量异步任务时候线程池能够提供较好的性能,这是因为使用线程池可以使每个任务的调用开销减少。
2.线程池提供了一种资源限制和管理的手段,比如当执行一系列任务时候对线程的管理,每个ThreadPoolExecutor也保留了一些基本的统计数据,比如当前线程池完成的任务数目。
为什么系统会给我们提供线程池呢?
新建线程的缺点如下:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
如何使用ThreadPoolExecutor
1.使用Executors工具类(让我想到了Collections)里面已有的静态方法创建
val newFixedThreadPool = Executors.newFixedThreadPool(20)
val newSingleThreadExecutor = Executors.newSingleThreadExecutor()
val newCachedThreadPool = Executors.newCachedThreadPool()
val newScheduledThreadPool = Executors.newScheduledThreadPool(20)
2.ThreadPoolExecutort提供了四个构造方法,我们可以直接创建自定义的线程池,方法1 本质上也是通过ThreadPoolExecutort创建的,只是帮我们封装好了。
相关参数解释如下:
序号 | 参数名 | 参数类型 | 参数含义 |
---|---|---|---|
1 | corePoolSize | int | 核心线程池大小 |
2 | maximumPoolSize | int | 最大线程池大小 |
3 | keepAliveTime | long | 线程最大空闲时间 |
4 | unit | TimeUnit | 时间单位 |
5 | workQueue | BlockingQueue |
线程等待队列 |
6 | threadFactory | ThreadFactory | 线程创建工厂 |
7 | handler | RejectedExecutionHandler | 拒绝策略 |
我们先从系统提供的方法来深入了解下每个参数的含义
1.newFixedThreadPool(20)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
这个方法有1 个参数,int threads 表示线程池中的数量,这个参数会直接给ThreadPoolExecutor的第一和第二个参数,也就是corePoolSize和maximumPoolSize,剩下的参数中空余时间给的是 0,创建了一个新建了阻塞队列LinkedBlockingQueue,返回值是ExecutorService。
2.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
这个方法没有参数,和newFixedThreadPool区别是corePoolSize和maximumPoolSize两个参数给了都是 1,需要注意这里最终返回的是FinalizableDelegatedExecutorService
。有个问题想一想,一个线程也要用线程池么?意义在哪里?
3.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
这个看起来参数都挺全的,首先corePoolSize是 0,maximumPoolSize给了 int 最大值,keepAliveTime是 60 ,时间单位是秒,这里注意等待队列和前面两个不一样这里是SynchronousQueue。返回值是ExecutorService。
4.newScheduledThreadPool(20)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
这个高级点了,自己是个对象当然是集成ThreadPoolExecutor类,我们看下他的构造方法有两个,这里直接看一个参数的构造方法。返回值是ScheduledThreadPoolExecutor,这里注意和前面三个都不一样。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
同样这里入参和清晰了,参数名是corePoolSize核心线程数量直接给了我们ThreadPoolExecutor的第一个参数corePoolSize,第二个maximumPoolSize最大线程池大小和newCachedThreadPool一样给的是 int 最大值,第三个看起来是个常量我们看下代码中是 DEFAULT_KEEPALIVE_MILLIS = 10L
,单位是秒,这里的等待队列和前面的也不一样是DelayedWorkQueue优先级队列。
到此系统提供的四种创建线程池的参数都一一看了一遍,接下来我们看下为什么系统要提供这四种方法给我们,根据构造方法中的参数,有什么不一样的使用场景,可以先想一想不着急忘记下。
现在就开始分析下四个方法创建出的线程池的使用场景。
1. newFixedThreadPool(白话文:固定数目的线程池)
通过上面的构造方法我们知道,newFixedThreadPool核心线程池等于最大线程池,当前的线程数能够比较稳定保证一个数。能够避免频繁回收线程和创建线程。故适用于处理cpu密集型的任务,确保cpu在长期被工作线程使用的情况下,尽可能少的分配线程,即适用长期的任务。
缺点:
达到线程池最大容量后,如果有任务完成让出占用线程,那么此线程就会一直处于等待状态,而不会消亡,直到下一个任务再次占用该线程。这就可能会使用无界队列来存放排队任务,当大量任务超过线程池最大容量需要处理时,队列无线增大,会占用大量资源。
2. newSingleThreadExecutor(白话文:唯一线程线程池)
由于是唯一的嘛,最适用串行化任务。
3. newCachedThreadPool(白话文:可缓存线程的线程池)
newCacehedThreadPool 的最大特点就是,线程数量不固定。只要有空闲线程空闲时间超过keepAliveTime,就会被回收。有新的任务,查看是否有线程处于空闲状态,如果不是就直接创建新的任务。故适用用于并发不固定的短期小任务。
缺点:
线程池没有最大线程数量限制,如果大量的任务同时提交,可能导致创线程过多会而导致资源耗尽。
4. newScheduledThreadPool(白话文:定时及周期执行的线程池)
适用于定时操作一些任务