自定义线程池
核心代码段:
适用场景
- 延迟敏感型任务:优先用线程处理,避免队列等待。
- 突发流量:快速扩容线程应对短期高峰。
- 需要明确拒绝反馈:异常信息包含完整状态。
// 更健壮的实现
public ThreadPoolExecutor createThreadFirstPool() {
int core = 10;
int max = 50;
int queueSize = 100;
// V1: 任何情况都优先创建线程
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(queueSize) {
@Override
// 将元素插入队列
public boolean offer(Runnable e) {
// 先尝试返回false让线程池扩容
return false;
}
};
// V2: 可增加队列检查,避免完全放弃队列缓冲
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(queueSize) {
// 当前线程数 < core时仍入队(保留核心线程的缓冲能力)
if (executor.getPoolSize() < core) {
return super.offer(e);
}
return false;
};
return new ThreadPoolExecutor(
core,
max,
60,
TimeUnit.SECONDS,
queue,
new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "tpool-" + count.getAndIncrement());
}
},
(r, executor) -> {
// 拒绝时的处理:最后一次尝试入队
if (!executor.getQueue().offer(r)) {
throw new RejectedExecutionException(
"线程池已满(" + executor.getPoolSize() + "/" + max + "), " +
"队列已满(" + executor.getQueue().size() + "/" + queueSize + ")");
}
}
);
}
代码详解
这段代码实现了一个自定义线程池,核心设计思路是优先创建线程而非入队(与JDK默认策略相反),并通过巧妙的队列控制实现这一目标。以下是详细解析:
1. 核心参数
int core = 10; // 核心线程数
int max = 50; // 最大线程数
int queueSize = 100; // 队列容量
2. 关键设计:欺骗性队列
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(queueSize) {
@Override
public boolean offer(Runnable e) {
return false; // 永远拒绝入队,迫使线程池扩容
}
};
- 默认行为:JDK的
ThreadPoolExecutor在任务提交时,优先入队,队列满后才创建非核心线程。 - 本实现:通过重写
offer()方法永远返回false,让线程池认为队列已满,从而直接触发线程创建(直到达到max)。
3. 线程工厂
new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "tpool-" + count.getAndIncrement());
}
}
- 为线程命名(如
tpool-1),便于监控和日志排查问题。
4. 拒绝策略增强
(r, executor) -> {
if (!executor.getQueue().offer(r)) { // 最后一次尝试入队
throw new RejectedExecutionException(
"线程池已满(" + executor.getPoolSize() + "/" + max + "), " +
"队列已满(" + executor.getQueue().size() + "/" + queueSize + ")");
}
}
- 拒绝时:先尝试将任务真正放入队列(此时队列可能因其他线程消费而有空间)。
- 若仍失败:抛出包含详细信息的异常(当前线程数/队列大小)。
5. 执行流程
- 提交任务时,线程池调用队列的
offer()方法。 - 由于
offer()永远返回false,线程池直接尝试创建新线程(不超过max)。 - 当线程数达到
max后,触发拒绝策略,此时再尝试一次真实入队。 - 若队列确实已满,抛出明确的异常。
7. 潜在问题
- 线程爆炸:如果任务持续激增且处理慢,可能快速达到
max。 - 资源浪费:大量线程存活可能导致内存或CPU过载。
- 与默认策略对比:
- 默认:
core -> 队列 -> max -> 拒绝 - 本实现:
core -> max -> 队列 -> 拒绝
- 默认:
8. 改进建议
// 可增加队列检查,避免完全放弃队列缓冲
@Override
public boolean offer(Runnable e) {
// 当前线程数 < core时仍入队(保留核心线程的缓冲能力)
if (executor.getPoolSize() < core) {
return super.offer(e);
}
return false;
}
这种设计适合需要低延迟但能承受更高资源开销的场景,需根据实际需求权衡。
改进后所能解决的问题
在第8点的改进中,我们通过有条件地控制队列的offer()行为,使得线程池在核心线程未满时仍使用队列缓冲,而在核心线程已满后优先扩容线程。这种调整解决了原始实现中的几个关键问题,同时保留了“优先扩容线程”的核心优势。以下是具体解决的问题和带来的好处:
1. 解决核心线程闲置问题
原始问题:
- 原始实现中,
offer()永远返回false,导致所有任务都会直接触发线程创建(即使核心线程空闲)。 - 这会导致核心线程未被充分利用,而频繁创建非核心线程,违背了线程池“核心线程优先”的设计初衷。
改进后:
- 当
当前线程数 < core时,任务正常入队,由核心线程逐步消费。 - 优势:
- 充分利用核心线程的缓冲能力,避免不必要的线程创建。
- 符合线程池“核心线程优先”的设计原则,减少资源浪费。
2. 避免突发流量下的线程爆炸
原始问题:
- 如果瞬间提交大量任务,原始实现会立即创建大量线程(直到
max),即使这些任务是短暂的突发流量。 - 可能导致线程数快速达到上限,后续任务直接触发拒绝策略。
改进后:
- 在核心线程未满时,任务先入队,由核心线程平稳消费。
- 只有核心线程繁忙时(
poolSize >= core),才触发线程扩容。 - 优势:
- 平滑突发流量:短期的任务高峰会被队列缓冲,避免线程数瞬间飙升。
- 更符合实际场景:多数情况下,核心线程足以处理常态负载。
3. 平衡延迟与资源开销
原始问题:
- 原始实现完全放弃队列缓冲,所有任务依赖线程处理,可能导致:
- 高线程数带来的上下文切换开销。
- 内存占用增加(每个线程需要独立的栈空间)。
改进后:
- 在核心线程空闲时,任务入队,减少线程数量。
- 在核心线程繁忙时,优先扩容线程而非入队,保证低延迟。
- 优势:
- 动态权衡:根据负载自动选择“低延迟”(线程扩容)或“低开销”(队列缓冲)。
- 更适合混合型负载(既有突发流量,又有常态任务)。
4. 拒绝策略的合理性
原始问题:
- 原始实现中,拒绝策略触发的条件是“线程数达到
max且队列真正满”。 - 但在此之前,可能已经因为过度创建线程导致资源紧张。
改进后:
- 拒绝策略的触发更接近实际资源极限:
- 线程数达到
max(资源硬限制)。 - 队列满(缓冲能力耗尽)。
- 线程数达到
- 优势:
- 拒绝时的异常信息更准确反映系统真实状态。
适用场景
这种改进后的策略特别适合以下场景:
- 常态低负载+偶发高峰:核心线程处理日常任务,突发流量触发扩容。
- 需要兼顾吞吐量和延迟:核心线程保证吞吐量,非核心线程优化延迟。
- 资源敏感环境:避免无限制的线程创建,同时保留弹性。
对比总结
| 场景 | 原始实现 | 改进后实现 |
|---|---|---|
| 核心线程利用率 | 低(总直接扩容) | 高(优先用核心线程) |
| 突发流量处理 | 立即扩容,可能线程爆炸 | 队列缓冲后逐步扩容 |
| 资源开销 | 高(线程多) | 动态平衡(核心线程+弹性扩容) |
| 拒绝策略触发时机 | 线程数=max且队列真满 | 线程数=max且队列真满 |
改进后的实现更智能、更贴近实际需求,既保留了“优先扩容线程”的目标,又避免了极端情况下的资源浪费。

浙公网安备 33010602011771号