代码网 logo
AI

Java中的线程池用过吧?来说说你是怎么理解线程池吧?

小开开2021-05-06 09:15:0254

前言

Java中的线程池用过吧?来说说你是怎么使用线程池的?这句话在面试过程中遇到过好几次了。我甚至这次标题都想写成【Java八股文之线程池】,但是有点太俗套了。虽然,线程池是一个已经被说烂的知识点了,但是还是要写这一篇用来加深自己的印象,但是想使用一个特殊的方式写出来。

线程池

使用线程池的目的

先说一下我们为什么要使用线程池?

  • 线程是稀缺资源,不能频繁的创建。而且创建和销毁线程也是比较占用系统开销的。
  • 为了做到解耦,线程的创建与执行任务分开,方便对线程进行维护。
  • 为了复用,前面也说了创建和销毁线程比较耗系统开销,那么创建出来线程放到一个池子里,可以给其他任务进行复用。

线程池是如何一步一步创建的

第一版

正常的我们在创建一个线程去执行任务的时候是这样的:

复制代码
new Thread(r).start();

但是这是最基本的方式,我们的项目中有可能很多地方都需要创建一个新的线程。这个使用为了减少重复代码,我们会把这段创建线程的代码放的一个工具类里面,然后对外提供工具方法,使用的时候直接调用此方法即可。
第二版

复制代码
/**
 * 先定义接口(任务执行器)
 */
public interface Executor {
    /**
     * 执行任务
     * @param runnable  线程任务
     */
    void execute(Runnable runnable);
}
复制代码
/**
 * 实现:直接创建线程。
 */
class ExecutorImpl implements Executor {
    public void execute(Runnable r) {
        new Thread(r).start();
    }
}

这种方式实现了创建线程的代码的复用,但是并没有实现线程资源的复用,当有1000个地方需要线程的时候,会创建1000个线程。
第三版

为了实现资源也复用,增加一个阻塞队列,当来了创建线程的任务的时候,先放到队列里,然后再用一个线程(Worker),来处理任务。这样就完成了线程资源的复用了,全程只有一个线程在来回的复用,一直在处理队列中的任务。
有了队列的线程池

通过上面的方式,实现了线程资源的复用,并且也起到提交任务和处理任务之间的解耦。但是只有一个线程处理任务,会有瓶颈的,所以具体需要多少线程来处理任务最好是根据具体的业务场景来确定,这样我们把这个值,设置成一个参数,当创建线程池的时候传入,就叫

复制代码
corePoolSize

吧。

而且任务队列最好也要有容量,但也应该是根据业务场景来配置容量,而且任务队列还可以定制一些规则,例如:按照一定的规则出队。所以我们把任务队列也配置成参数,在创建线程池的时候传入。参数名称就叫:

复制代码
workQueue

吧。

当队列中任务满了之后,任务就会被抛弃,但是如果是重要业务任务,还不能抛弃,所以,当队列中任务满了之后,在线程池没有资源处理任务的时候,拒绝策略,我们也根据业务场景来确定,这样也在创建的时候传入一种拒绝策略,参数名称就叫:

复制代码
rejectedExecutionHandler

继续优化

虽然多了上面的三个参数后效果优化了不少,但是还可以继续优化:

  • 并不用上来就创建
复制代码
corePoolSize

数量的线程,我们可以增加了一个变量

复制代码
workCount

,来记录已经创建出来了工作线程,这样在初始化的时候只有

复制代码
workCount<corePoolSize

的时候,我们才创建线程来执行任务,当

复制代码
workCount>CorePoolSize

的时候,再来了任务,就去进队列。

  • 在增加拒绝策略的时候,我定义一个接口:
复制代码
RejectedExecutionHandler

,然后使用者可以自己去实现这个接口,来完成自己的拒绝策略。

  • 增加一个线程工厂的入参:
复制代码
ThreadFactory

,这样保证每次创建线程的时候不用手动去创建线程了,而是通过

复制代码
ThreadFactory

来获取线程,并且也可以增加一些线程的标识。
第四版

虽然说第三版的线程池已经可以应对日常工作中的情况了,但是还是不够有弹性,所谓的弹性就是指,在任务提交频繁时应该处理能力提高,任务提交不频繁时处理能力应该降低

上面这版线程池就不够弹性。

如果某个时间段,任务提交量剧增,这个时候,

复制代码
corePoolSize

和队列都满了,再来提交任务就只能走拒绝策略了。

你或许会想到,那我可以增大

复制代码
corePoolSize

的值,这样就会创建出来更多的线程来处理任务,但是这个任务提交量剧增,只是某个时间段,过了这个时间段之后,创建出来这么多的线程,可以大部分都会是空闲的状态。这样也是浪费资源了。

这样就导致了一个两难的情况,

复制代码
corePoolSize

的值设置太大了也不好,设置太小了也不好。

这个时候,为让线程池做到弹性伸缩,我们可以为他再添加一个参数:

复制代码
maximumPoolSize

,这个参数代表的意思是最大线程数。

复制代码
corePoolSize

复制代码
workQueue

都满了的时候,新提交的任务仍然可以创建新线程来进行处理,这些超过

复制代码
corePoolSize

创建出来的线程,被称为非核心线程。当

复制代码
corePoolSize

与非核心线程数量的和等于

复制代码
maximumPoolSize

再执行拒绝策略。
最大线程数量
通过这样的方式,

复制代码
corePoolSize

,负责平时情况的线程使用量,

复制代码
maximumPoolSize

负责提交任务高峰时的,临时扩充容量。

但是目前这样的方式只是考虑到了提交任务量高峰时期的扩充,但这个高峰期只是暂时的,过了这个高峰期,非核心线程一直放着也是浪费资源,所以我们再设定一个非核心线程的空闲活跃时间的参数:

复制代码
keepAliveTime

,这样当非核心线程数,空闲时间超过这个值就销毁线程,释放资源

广告