线程池的创建及使用

java中给我们提供了一个Executors类来方便的获取一个线程池。可以通过如下几个静态方法方便的获取不同类型的线程池:

Executors.newCachedThreadPool();
Executors.newFixedThreadPool(8);
Executors.newScheduledThreadPool(8);
Executors.newSingleThreadExecutor();
Executors.newWorkStealingPool();

通过源码我们可以整理出这么一个表格

线程池使用的实现类
CachedThreadPool
ThreadPoolExecutor
FixedThreadPool
ThreadPoolExecutor
ScheduledThreadPool
ScheduledThreadPoolExecutor继承ThreadPoolExecutor
SingleThreadExecutor
FinalizableDelegatedExecutorService代理ThreadPoolExecutor
WorkStealingPool(1.8)
ForkJoinPool

可以看到除1.8新增的WorkStealingPool以外,其余4个线程池都直接或者间接使用的实现类为ThreadPoolExecutor,那我们就从他开始学习。

ThreadPoolExecutor的类图如上,他有4个构造方法(只是入参的数量差别),其中参数最多最全的一个构造方法有7个入参。

int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler

int corePoolSize 核心线程数

1、新建线程时,当前线程总数<corePoolSize则新建核心线程,否则新建非核心线程

2、核心线程默认会一直存活在线程池中,非核心线程在空闲超过指定时间后会被销毁

3、如果线程池的allowCoreThreadTimeOut属性为true,那么核心线程闲置超过keepAliveTime时间时(时间单位为纳秒),核心线程也会被销毁

int maximumPoolSize 最大线程数

1、最大线程数=核心线程数+非核心线程数

long keepAliveTime 闲置超时时长

1、非核心线程空闲超过此时间后会被销毁

2、核心线程在allowCoreThreadTimeOut属性为true的情况下,空闲超过此时间后一样会被销毁

TimeUnit unit 时间单位

1、keepAliveTime的时间单位,会通过这个参数将keepAliveTime转化成纳秒保存

BlockingQueue<Runnable> workQueue 任务队列

1、用来存放线程池待执行的Runnable对象

2、核心线程没有空闲时,新添加的任务会被添加到这个队列。只有当任务队列满了,才会新建非核心线程执行任务

3、常见workQueue类型如下:

SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大

LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize

ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误

DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

ThreadFactory threadFactory 创建线程的工厂

1、创建线程的工厂,用于把Runnable对象变成线程对象

RejectedExecutionHandler handler 线程饱和策略

1、当线程池和任务队列都满了,此时再加入新任务时执行的策略(有点类似于异常处理)

在使用线程池的时候,我们可以选择自己去new一个ThreadPoolExecutor对象,也可以直接使用Executors类的静态方法。Executors提供的几个不同类型的线程池区别如下:

线程池用途
CachedThreadPool
用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)
FixedThreadPool
创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)
ScheduledThreadPool
适用于执行延时或者周期性任务。
SingleThreadExecutor
创建一个单线程的线程池,适用于需要保证顺序执行各个任务。
WorkStealingPool(1.8)

并行的线程池,不会保证任务顺序执行,抢占式的工作。

线程池创建好了,如何使用线程池来执行任务呢?

通常情况下我们可以使用Executor接口中的void execute(Runnable command);方法来提交一个任务到线程池。

如果需要获取任务的返回值的话,则需要使用ExecutorService接口中的submit方法,submit有3个不同的重载方法,返回值都是Future类,通过Future类即可获取任务的返回值。

参考资料:

https://www.jianshu.com/p/210eab345423

https://www.jianshu.com/p/7726c70cdc40

支付宝搜索:344355 领取随机红包

如果文章对您有帮助,欢迎给作者打赏