设计与调优 - 线程池面试题及答案
15.线程池如何实现参数的动态修改?
线程池提供了几个 setter方法来设置线程池的参数。
![JDK 线程池参数设置接口来源参考[7]](https://web-1259428203.cos.ap-chengdu.myqcloud.com/6464c6690d2dde5777cbc3b2.png)
这里主要有两个思路:

在我们微服务的架构下,可以利用配置中心如Nacos、Apollo等等,也可以自己开发配置中心。业务服务读取线程池配置,获取相应的线程池实例来修改线程池的参数。
如果限制了配置中心的使用,也可以自己去扩展ThreadPoolExecutor,重写方法,监听线程池参数变化,来动态修改线程池参数。
16.线程池调优了解吗?
线程池配置没有固定的公式,通常事前会对线程池进行一定评估,常见的评估方案如下:
![线程池评估方案 来源参考[7]](https://web-1259428203.cos.ap-chengdu.myqcloud.com/6464c5f00d2dde5777cb5100.png)
上线之前也要进行充分的测试,上线之后要建立完善的线程池监控机制。
事中结合监控告警机制,分析线程池的问题,或者可优化点,结合线程池动态参数配置机制来调整配置。
事后要注意仔细观察,随时调整。

具体的调优案例可以查看参考[7]美团技术博客。
17.你能设计实现一个线程池吗?
⭐这道题在阿里的面试中出现频率比较高
线程池实现原理可以查看 要是以前有人这么讲线程池,我早就该明白了! ,当然,我们自己实现, 只需要抓住线程池的核心流程-参考[6]:

我们自己的实现就是完成这个核心流程:
- 线程池中有N个工作线程
- 把任务提交给线程池运行
- 如果线程池已满,把任务放入队列
- 最后当有空闲时,获取队列中任务来执行
实现代码[6]:
public class MyThreadPoolExecutor implements Executor {
//记录线程池中线程数量
private final AtomicInteger ctl = new AtomicInteger(0);
//核心线程数
private volatile int corePoolSize;
//最大线程数
private volatile int maximumPoolSize;
//阻塞队列
private final BlockingQueue<Runnable> workQueue;
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, BlockingQueue<Runnable> workQueue) {
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
}
/**
* 执行
*
* @param command
*/
@Override
public void execute(Runnable command) {
//工作线程数
int c = ctl.get();
//小于核心线程数
if (c < corePoolSize) {
//添加任务失败
if (!addWorker(command)) {
//执行拒绝策略
reject();
}
return;
}
//任务队列添加任务
if (!workQueue.offer(command)) {
//任务队列满,尝试启动线程添加任务
if (!addWorker(command)) {
reject();
}
}
}
/**
* 饱和拒绝
*/
private void reject() {
//直接抛出异常
throw new RuntimeException("Can not execute!ctl.count:"
+ ctl.get() + "workQueue size:" + workQueue.size());
}
/**
* 添加任务
*
* @param firstTask
* @return
*/
private boolean addWorker(Runnable firstTask) {
if (ctl.get() >= maximumPoolSize) return false;
Worker worker = new Worker(firstTask);
//启动线程
worker.thread.start();
ctl.incrementAndGet();
return true;
}
/**
* 线程池工作线程包装类
*/
private final class Worker implements Runnable {
final Thread thread;
Runnable firstTask;
public Worker(Runnable firstTask) {
this.thread = new Thread(this);
this.firstTask = firstTask;
}
@Override
public void run() {
Runnable task = firstTask;
try {
//执行任务
while (task != null || (task = getTask()) != null) {
task.run();
//线程池已满,跳出循环
if (ctl.get() > maximumPoolSize) {
break;
}
task = null;
}
} finally {
//工作线程数增加
ctl.decrementAndGet();
}
}
/**
* 从队列中获取任务
*
* @return
*/
private Runnable getTask() {
for (; ; ) {
try {
System.out.println("workQueue size:" + workQueue.size());
return workQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//测试
public static void main(String[] args) {
MyThreadPoolExecutor myThreadPoolExecutor = new MyThreadPoolExecutor(2, 2,
new ArrayBlockingQueue<Runnable>(10));
for (int i = 0; i < 10; i++) {
int taskNum = i;
myThreadPoolExecutor.execute(() -> {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务编号:" + taskNum);
});
}
}
}这样,一个实现了线程池主要流程的类就完成了。
18.单机线程池执行断电了应该怎么处理?
我们可以对正在处理和阻塞队列的任务做事务管理或者对阻塞队列中的任务持久化处理,并且当断电或者系统崩溃,操作无法继续下去的时候,可以通过回溯日志的方式来撤销正在处理的已经执行成功的操作。然后重新执行整个阻塞队列。
也就是:阻塞队列持久化;正在处理任务事务控制;断电之后正在处理任务的回滚,通过日志恢复该次操作;服务器重启后阻塞队列中的数据再加载。
作者:三分恶
来源:https://www.cnblogs.com/three-fighter/p/15565691.html
最近建一些几十个工作内推群,各大城市都有,群里目前已经收集了很多内推岗位,大厂、中厂、小厂、外包都有。 欢迎HR、开发、测试、运维和产品加入。

扫描下方微信,备注:网站+所在城市,即可拉你进工作内推群。
