线程池,面试问题集

为什么是需要使用线程池?

如果直接创建线程,存在以下问题:
1、反复创建线程系统开销比较大,每个线程的创建和销毁都需要时间,如果线程执行的任务比较简单,那么创建销毁线程占用资源比线程本身执行任务销毁的资源还要多。
2、过多的线程会消耗更多的内存,也会造成过多的上下文切换,使得系统不稳定。
使用线程池优点:
1、线程池可以控制线程生命周期的开销问题,同时加快响应速度。线程池的线程是可以复用的,通过少量的线程执行大量的任务。
2、线程池可以统筹内存和CPU的使用,避免资源使用不当。线程池可以根据配置和任务数量灵活控制线程数量。
3、线程池可以统一管理资源。

线程池各个参数的含义

属性名字作用
corePoolSize核心线程数当线程池运行线程少于corePoolSize时,将创建一个新的线程来处理请求,即使其他线程处于空闲状态
threadFactory线程工厂用于创建线程的工厂
workQueue队列用于保留任务并移交工作线程的阻塞队列
maximunPoolSize最大线程数线程池允许开始最大线程数
handler拒绝策略在线程池添加任务时候,两种情况下触发拒绝策略:1、线程池运行状态不是RUNNING;2、线程池达到最大线程数,并且阻塞队列已经满。
keepAliveTime保持存活时间当线程池线程数大于核心线程数时,多余的线程空闲时间超过keepAliveTime时会被终止。

线程创建时机

image.png

线程池状态

RUNNING:接受新任务并处理排队的任务
SHUTDOWN:不接受新任务,但处理排队的任务
STOP:不接受新任务,不处理排队的任务,并且中断进行中的任务
TIDYING:所有的任务都已经终止,workerCount为零,线程转换到TIDYING状态将运行terminated()钩子方法。
TERMINATED:terminated()已经完成

线程池状态转换

image.png

线程池有哪些拒绝策略

拒绝时间

1、线程池不在RUNNING状态,线程池调用shutdown等方法关闭线程池后,即便线程池内部依然存在没有执行完的任务正在执行,但是线程池已经关闭,此时再向线程池内提交任务,就会遭到拒绝。
2、线程池没有能力继续处理新提交的任务。线程池达到最大线程数,且队列已经满的情况。

4种拒绝策略

image.png
AbortPolicy:在拒绝任务时,会抛出异常。(异常:RejectedExecutionException 的 RuntimeException)
DiscardPolicy:当提交新任务时候,直接抛弃掉,不做任何通知。
DiscardOldestPolicy:当提交新任务时候,丢弃掉存活时间长的队列头任务,腾出空间给新的任务
CallerRunsPolicy:当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。

阻塞队列

ArrayBlockingQueue:基于数组结构的有界阻塞队列,按先进先出对元素进行排序。
LinkedBlockingQueue:基于链表结构的有界/无界阻塞队列,按先进先出对元素进行排序,吞吐量通常高于 ArrayBlockingQueue。Executors.newFixedThreadPool 使用了该队列。
SynchronousQueue:不是一个真正的队列,而是一种在线程之间移交的机制。要将一个元素放入 SynchronousQueue 中,必须有另一个线程正在等待接受这个元素。如果没有线程等待,并且线程池的当前大小小于最大值,那么线程池将创建一个线程,否则根据拒绝策略,这个任务将被拒绝。使用直接移交将更高效,因为任务会直接移交给执行它的线程,而不是被放在队列中,然后由工作线程从队列中提取任务。只有当线程池是无界的或者可以拒绝任务时,该队列才有实际价值。Executors.newCachedThreadPool使用了该队列。
PriorityBlockingQueue:具有优先级的无界队列,按优先级对元素进行排序。元素的优先级是通过自然顺序或 Comparator 来定义的。

线程池线程数多少合适?

线程池处理的任务通常分为cpu密集型任务、IO密集型任务。

cpu密集型任务核心线程数设置

加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务

核心线程数

这样的任务最佳的线程数为 CPU 核心数的 1~2 倍

原因

因为计算任务非常重,会占用大量cpu资源,此时每个核心线程都是满负荷工作。设置过多的线程数会造成不必要的上下文切换。

IO密集型任务核心线程数设置

数据库、文件的读写,网络通信等任务,这种任务的特点是并不会特别消耗 CPU 资源,但是 IO 操作很耗时,总体会占用比较多的时间。

核心线程数

最大线程数一般会大于 CPU 核心数很多倍
线程数 = CPU 核心数 *(1+平均等待时间/平均工作时间)
例如我们有个定时任务,部署在4核的服务器上,该任务有100ms在计算,900ms在I/O等待,则线程数约为:4 * 1 * (1 + 900 / 100) = 40个。

原因

因为 IO 读写速度相比于 CPU 的速度而言是比较慢的,如果我们设置过少的线程数,就可能导致 CPU 资源的浪费。而如果我们设置更多的线程数,那么当一部分线程正在等待 IO 的时候,它们此时并不需要 CPU 来计算,那么另外的线程便可以利用 CPU 去执行其他的任务,互不影响,这样的话在任务队列中等待的任务就会减少,可以更好地利用资源。

使用线程池队列需要注意什么?

有界队列:需要注意线程池满后。被拒绝的任务如何处理。
无界队列:如果提交任务大于线程池处理任务的速度时候 ,可能导致内存溢出。

线程只能在任务到达时才启动吗?

默认情况下,即使是核心线程也只能在新任务到达时才创建和启动。但是我们可以使用 prestartCoreThread(启动一个核心线程)或 prestartAllCoreThreads(启动全部核心线程)方法来提前启动核心线程。

核心线程怎么实现一直存活?

|
| 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
| — | — | — | — | — |
| 插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
| 移除 | remove() | poll() | take() | poll(time,unit) |
| 检查 | element() | peek() | 不可用 | 不可用 |

核心线程在获取任务时,通过阻塞队列的 take() 方法实现的一直阻塞(存活)。

非核心线程如何实现在 keepAliveTime 后死亡?

原理同上,也是利用阻塞队列的方法,在获取任务时通过阻塞队列的 poll(time,unit) 方法实现的在延迟死亡。

非核心线程能成为核心线程吗?

虽然我们一直讲着核心线程和非核心线程,但是其实线程池内部是不区分核心线程和非核心线程的。只是根据当前线程池的工作线程数来进行调整,因此看起来像是有核心线程于非核心线程。

如何终止线程池?

终止线程池主要有两种方式:
shutdown:“温柔”的关闭线程池。不接受新任务,但是在关闭前会将之前提交的任务处理完毕。
shutdownNow:“粗暴”的关闭线程池,也就是直接关闭线程池,通过 Thread#interrupt() 方法终止所有线程,不会等待之前提交的任务执行完毕。但是会返回队列中未处理的任务。

线程池ctl

ctl怎么设计的?

ctl是打包两个概念字段的原子整数。
workerCount:指示线程的有效数量。
runState:线程池状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED
ctl int类型、32位。高3位为runState的,低29位为workerCount。
image.png
例如,当我们的线程池运行状态为 RUNNING,工作线程个数为3,
则此时 ctl 的原码为:1010 0000 0000 0000 0000 0000 0000 0011

为什么要这样设计?

runState 和 workerCount 是线程池正常运转中的2个最重要属性,线程池在某一时刻该做什么操作,取决于这2个属性的值。
因此无论是查询还是修改,我们必须保证对这2个属性的操作是属于“同一时刻”的,也就是原子操作,否则就会出现错乱的情况。如果我们使用2个变量来分别存储,要保证原子性则需要额外进行加锁操作,这显然会带来额外的开销,而将这2个变量封装成1个 AtomicInteger 则不会带来额外的加锁开销,而且只需使用简单的位操作就能分别得到 runState 和 workerCount。
由于这个设计,workerCount 的上限 CAPACITY = (1 << 29) - 1,对应的二进制原码为:0001 1111 1111 1111 1111 1111 1111 1111(不用数了,29个1)。
通过 ctl 得到 runState,只需通过位操作:ctl & ~CAPACITY。
(按位取反),于是“CAPACITY”的值为:1110 0000 0000 0000 0000 0000 0000 0000,只有高3位为1,与 ctl 进行 & 操作,结果为 ctl 高3位的值,也就是 runState。
通过 ctl 得到 workerCount 则更简单了,只需通过位操作:c & CAPACITY。

面试必问的线程池,你懂了吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后端马农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值