面试官:线程池你知道多少?展开讲讲!

128 阅读6分钟

第一招:

看你简历中用了线程池,先来讲讲创建线程池的目的。

这里借用《Java 并发编程的艺术》书中的部分内容来总结一下使用线程池的好处:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池一般用于执行多个不相关联的耗时任务,没有多线程的情况下,任务顺序执行,使用了线程池的话可让多个不相关联的任务同时执行。

创建线程池有哪些方法?

线程池有两种创建方式,手动创建和一键创建

方式一:通过ThreadPoolExecutor构造函数来创建(推荐)。

image.png

方式二:通过 Executor 框架的工具类 Executors 来创建。

我们可以创建多种类型的 ThreadPoolExecutor

  • FixedThreadPool:该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
  • SingleThreadExecutor 该方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
  • CachedThreadPool 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
  • ScheduledThreadPool:该返回一个用来在给定的延迟后运行任务或者定期执行任务的线程池。

第2招

线程池的核心参数有哪些?

  • corePoolSize : 任务队列未达到队列容量时,最大可以同时运行的线程数量。

  • maximumPoolSize : 任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。

  • workQueue: 新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

  • keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,多余的空闲线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁,线程池回收线程时,会对核心线程和非核心线程一视同仁,直到线程池中线程的数量等于 corePoolSize ,回收过程才会停止。

  • unit : keepAliveTime 参数的时间单位。

  • threadFactory :executor 创建新线程的时候会用到。

  • handler :饱和策略。关于饱和策略下面单独介绍一下。

拒绝策略展开讲讲

  • ThreadPoolExecutor.AbortPolicy 摆烂,直接抛出异常不处理了。

  • ThreadPoolExecutor.CallerRunsPolicy 让调用线程池的线程来执行(交给大哥)

  • ThreadPoolExecutor.DiscardPolicy 摆!直接把任务抛弃,异常都不抛。

  • ThreadPoolExecutor.DiscardOldestPolicy 把等待时间最久的抛弃(不讲武德)。

线程池的启动流程了解吗?

image.png

  1. 核心线程池(相当于精英部队)未满,优先出动。
  2. 精英部队都在忙,交给等待队列,等着吧。
  3. 等的位置都没了,但单位里还有二等兵,出动!。
  4. 一个能打的都没了,看看领导的拒绝策略。

第三招

聊聊线程池的业务逻辑

如何优雅的关闭线程池

线程池提供了两个关闭方法:

  • shutdown() :线程池不再接受新任务了,等队列里的任务执行完毕则关闭。(温柔)
  • shutdownNow() :终止当前正在运行的任务,停止处理排队的任务并返回正在等待执行的 List。(暴力)

调用完 shutdownNowshuwdown 方法后,并不代表线程池已经完成关闭操作,它只是异步的通知线程池进行关闭处理。如果要同步等待线程池彻底关闭后才继续往下执行,需要调用awaitTermination方法进行同步等待。

也就是说调用shutdown后,需要调用awaitTermination方法设置合理超时时间,并用shutdownNow再次强制关闭还未结束的任务。另外,任务过程中可能抛出的异常也需要处理。

// 关闭线程池
executor.shutdown();
try {
    // 等待线程池关闭,最多等待5分钟
    if (!executor.awaitTermination(5, TimeUnit.MINUTES)) {
        // 如果等待超时,则打印日志
        System.err.println("线程池未能在5分钟内完全关闭");
    }
} catch (InterruptedException e) {
    // 异常处理
}

核心线程数的大小怎么设计

太多了,划水的人多。太少了,干活效率慢。

美团技术团队的思路是主要对线程池的核心参数实现自定义可配置。这三个核心参数是:

  • corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
  • maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
  • workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

如果我们的项目也想要实现这种效果的话,可以借助现成的开源项目:

  • Hippo4jopen in new window:异步线程池框架,支持线程池动态变更&监控&报警,无需修改代码轻松引入。支持多种使用模式,轻松引入,致力于提高系统运行保障能力。
  • Dynamic TPopen in new window:轻量级动态线程池,内置监控告警功能,集成三方中间件线程池管理,基于主流配置中心(已支持 Nacos、Apollo,Zookeeper、Consul、Etcd,可通过 SPI 自定义实现)。