自定义线程池

核心代码段:

适用场景

  • 延迟敏感型任务:优先用线程处理,避免队列等待。
  • 突发流量:快速扩容线程应对短期高峰。
  • 需要明确拒绝反馈:异常信息包含完整状态。
	// 更健壮的实现
    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. 执行流程

  1. 提交任务时,线程池调用队列的offer()方法。
  2. 由于offer()永远返回false,线程池直接尝试创建新线程(不超过max)。
  3. 当线程数达到max后,触发拒绝策略,此时再尝试一次真实入队。
  4. 若队列确实已满,抛出明确的异常。

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(资源硬限制)。
    • 队列满(缓冲能力耗尽)。
  • 优势
    • 拒绝时的异常信息更准确反映系统真实状态。


适用场景

这种改进后的策略特别适合以下场景:

  1. 常态低负载+偶发高峰:核心线程处理日常任务,突发流量触发扩容。
  2. 需要兼顾吞吐量和延迟:核心线程保证吞吐量,非核心线程优化延迟。
  3. 资源敏感环境:避免无限制的线程创建,同时保留弹性。

对比总结

场景 原始实现 改进后实现
核心线程利用率 低(总直接扩容) 高(优先用核心线程)
突发流量处理 立即扩容,可能线程爆炸 队列缓冲后逐步扩容
资源开销 高(线程多) 动态平衡(核心线程+弹性扩容)
拒绝策略触发时机 线程数=max且队列真满 线程数=max且队列真满

改进后的实现更智能、更贴近实际需求,既保留了“优先扩容线程”的目标,又避免了极端情况下的资源浪费。

posted @ 2025-04-26 15:38  月朗星希  阅读(91)  评论(0)    收藏  举报