java中不可避免的就是使用线程池TheadPoolExecutors,其中线程池的创建有两种方式:一种偷懒的做法就是通过Executors帮助类来简化线程池的创建
Executors.newFixedThreadPool
Executors.newFixedThreadPool
Executors.newSingleThreadExecutor
Executors.newCachedThreadPool
Executors.newScheduledThreadPool
Executors.newSingleThreadScheduledExecutor
另外一种是通过TheadPoolExecutor 来创建。虽说明面上创建方式是两种,但是底层实现就是一套,第一套创建方式是在第二套的基础上进一步封装而来的,既然明白了其中的关系,那么我们就直接跳过第一种方式,直接来看第二种实现方式,弄懂了第二种方式,第一种方式就水到渠成了。

通过类图可以看到线程池有两种提交任务的方式,一个是在Executor中定义的execute方法,另外一个是ExecutorService提供的submit,这两种方式的区别是execute直接执行提交的任务,而submit方法是提交任务后返回一个Future,通过Futur.get()我们可以获取到任务执行的结果,但是这种方式会阻塞当前的线程执行直到拿到返回结果,针对这种情况jdk8增加来CompletableFuture 异步执行的Future,这个东西我们后面再来针对的分析。
接下来我们看看如何创建TheadPoolExecutor,通过构造函数我们逐一说明一下各个参数的作用。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize 核心线程数
- 核心线程会一直存活,即使没有任务需要执行
- 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
- 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
maximumPoolSize 最大线程数
- 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
- 当线程数=maxPoolSize,且任务队列已满时,线程池会执行拒绝策略
keepAliveTime 线程空闲时间
- 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
- 如果allowCoreThreadTimeout=true,则会直到线程数量=0
TimeUnit 时间单位
workQueue 任务队列,用来指定线程池用什么队列来装载提交的任务。
- 如果已创建线程数小于corePoolSize,那么会创建新的线程来执行当前提交的任务,而不是进入阻塞队列
- 如果已创建线程数大于等于corePoolSize,会尝试先进入阻塞队列,如果进入失败(其实就是队列已满),则会在maxPoolSize条件下创建新的线程来执行当前提交的任务。如果不满足maxPoolSize条件,那么就会执行 拒绝执行策略(默认的拒绝执行策略见下)
- 通常有三种入队列策略
- 直接传递给线程(Direct handoffs)
比如:SychronousQueue
- 直接传递给线程(Direct handoffs)
感觉可以理解为这个入队列会总是失败,就相当于没有这个队列一样。这样就能在maxPoolSize条件下尽可能快的创建(或选择空闲的线程)来执行新提交的任务。
如果提交的任务有互相的依赖性,可以考虑使用这种队列。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 无界队列(Unbounded Queue)
比如:LinkedBlockingQueue
可以理解为如果有任务需要入队列,那么总会入队成功。
因此按照创建新线程的条件,理论上不会有超过corePoolSize个数的线程。也就是说理论上线程数最多为corePoolSize,因此maxPoolSize的设置也就显得没有意义了。如果提交的任务互相间没有依赖性,可以考虑使用这种队列 - 有界队列(Bounded Queue)
比如:ArrayBlockingQueue
如果使用有限个maxPoolSize,那么使用这种队列可以防止资源的耗尽。
使用长队列和小的线程池,可以降低CPU使用率,降低系统资源的消耗,以及降低线程上下文切换的消耗,但是会导致低吞吐量。如果任务频繁的阻塞,系统可能会创建比允许的线程数多的线程。
使用短队列和大的线程池,可以提高CPU使用率,但也有可能导致吞吐量下降。
threadFactory 线程工程,用来创建线程
handler 拒绝策略,当线程池已经满的时候再添加新的线程进来以什么方式进行处理,目前提供了四种
- CallerRunsPolicy 这个拒绝处理器,将直接运行这个任务的run方法。但是,请注意并不是在ThreadPoolExecutor线程池中的线程中运行,而是直接调用这个任务实现的run方法
- AbortPolicy 这个处理器,在任务被拒绝后会创建一个RejectedExecutionException异常并抛出
- DiscardPolicy DiscardPolicy处理器,将会默默丢弃这个被拒绝的任务,不会抛出异常,也不会通过其他方式执行这个任务的任何一个方法,更不会出现任何的日志提示
- DiscardOldestPolicy 这个处理器很有意思。它会检查当前ThreadPoolExecutor线程池的等待队列。并调用队列的poll()方法,将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行
网友评论