之前面试经常被问到ThreadPoolExecutor的参数怎么设置(面试总结点这里),面试题如下:
单机4核CPU,服务A超时时间是10s,A的方法耗时5s,其中调用B服务的方法耗时4.98s,请问
每秒100QPS访问,线程池各个参数如何设置?
首先我们得先了解下线程池 ThreadPoolExecutor各个参数的含义:
corePoolSize:线程池数量,每次有新任务请求都会增加一个线程,即使当前线程池有空闲线程,直到corePoolSize大小
queueCapacity: 当corePoolSize达到最大时,任务会被添加到阻塞队列等待执行,队列类型:
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列,使用LinkedBlockingQueue如果不指定大小,默认队列长度会设置为Integer.MAX_VALUE,那么将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增,导致服务超时并且可能会耗尽内存导致内存溢出。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列
maxPoolSize: 当corePoolSize达到最大时,阻塞队列也满了,那么线程池会继续增加线程,上线是maxPoolSize
RejectedExecutionHandler拒绝服务策略: 当maxPoolSize达到最大,任务数量任在继续增加,线程池处理能力达到最大,无法继续接受新任务,会拒绝接受新任务,服务拒绝策略有:
AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。
也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务
keepAliveTime: 一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程
问题解答:
A服务自身CPU耗时0.02s,1s内单线程并行度(能够处理任务数) 1 / 0.02 = 50,那么4核CPU1s内处理能力上线 4 * 50 = 200;每秒100QPS任务CPU可以处理的了,那么coreSize稳定在100,maxPoolSize=200;
服务A耗时5s,那么第6s结束的时候,第1s进入的100个任务才能全部结束,此时任务总数是6 * 100 = 600(每秒100),其中100个在线程阻塞状态等待B返回,那么等待队列size:600-coreSize = 500,这样队列最大等待时间是5s(每秒100QPS,到第6s的时候才会有100个任务结束出去,但是又会新增100任务,coreSize=100可以认为每秒没批次只能处理100,那么阻塞队列任务最大等待时间是5s),服务总耗时 = 等待时间 + 服务处理耗时 = 10s,未超时。那么线程keepAliveTime=10
总结:coreSize=100; maxPoolSzie=200;queueSize=500;keepAliveTime=10;服务决绝策略根据业务选定就好