百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

深度解析:Executors 工具类实现线程池的 4 种方式(含源码 + 避坑)

wptr33 2025-09-19 03:55 34 浏览

作为 Java 开发人员,
java.util.concurrent.Executor
s工具类一定不陌生 —— 它是 JDK 为简化线程池创建提供的 “快捷方式”,通过 4 个静态方法就能快速实例化不同类型的线程池,无需手动配置复杂参数。但 “快捷” 往往伴随着 “隐藏风险”,阿里巴巴 Java 开发手册明确禁止其在生产环境使用,核心原因就是部分实现存在资源耗尽隐患。

本文将从用法细节、源码原理、实战问题、替代方案四个维度,彻底讲透 Executors 工具类的 4 种线程池实现,帮你既懂 “怎么用”,更懂 “为什么不能这么用”。

Executors 工具类的设计初衷与底层逻辑

在拆解具体实现前,先明确一个核心认知:Executors 本质是 ThreadPoolExecutor 的 “封装器”

JDK 开发者考虑到新手难以掌握 ThreadPoolExecutor 的 7 个核心参数(如核心线程数、任务队列、拒绝策略等),便通过 Executors 封装了 4 种高频场景的线程池配置,让开发者只需一行代码即可创建线程池。其底层逻辑可概括为:

// Executors的静态方法 = 预设参数的ThreadPoolExecutor实例化
public static ExecutorService newFixedThreadPool(int nThreads) {
    // 核心参数已固化:核心线程数=最大线程数,无界队列,非核心线程存活时间0
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

所有 Executors 创建的线程池,最终都指向 ThreadPoolExecutor 实例,只是参数组合不同。而正是这些 “固化的参数”,导致了不同场景下的风险差异。

方式 1:newFixedThreadPool—— 固定大小的线程池

核心特性与适用场景

  • 线程数量固定:核心线程数(corePoolSize)= 最大线程数(maximumPoolSize),一旦创建不会增减,即使线程空闲也不会销毁(除非线程池关闭)。
  • 任务队列无界:使LinkedBlockingQueue(默认容Integer.MAX_VALUE,约 21 亿),可无限接收任务,避免任务被拒绝。
  • 适用场景:任务量已知、并发度固定的场景,如 “后台批量处理 1000 条订单数据,每次并发 5 个任务”“定时同步数据库表数据,固定 3 个线程执行”。

完整用法示例(含任务监控)

public class FixedThreadPoolDemo {
    public static void main(String[] args) {
        // 1. 创建固定大小为3的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        // 2. 提交10个任务(模拟批量处理)
        for (int i = 0; i < 10; i++) {
            int taskId = i;
            // 使用submit而非execute,可获取任务执行结果/异常
            Future<?> future = fixedThreadPool.submit(() -> {
                System.out.printf("[任务%d] 开始执行,线程名:%s%n", 
                                 taskId, Thread.currentThread().getName());
                try {
                    // 模拟任务耗时(1-3秒随机)
                    Thread.sleep(new Random().nextInt(2000) + 1000);
                } catch (InterruptedException e) {
                    System.out.printf("[任务%d] 被中断,原因:%s%n", taskId, e.getMessage());
                    Thread.currentThread().interrupt(); // 保留中断状态
                }
                System.out.printf("[任务%d] 执行完成%n", taskId);
                return taskId; // 任务执行结果
            });
            
            // 3. 监控任务结果(非必须,按需使用)
            try {
                Object result = future.get(5, TimeUnit.SECONDS); // 超时时间5秒
                System.out.printf("[任务%d] 结果:%s%n", taskId, result);
            } catch (InterruptedException e) {
                System.out.printf("[任务%d] 获取结果时被中断%n", taskId);
            } catch (ExecutionException e) {
                System.out.printf("[任务%d] 执行异常:%s%n", taskId, e.getCause().getMessage());
            } catch (TimeoutException e) {
                System.out.printf("[任务%d] 获取结果超时(5秒)%n", taskId);
                future.cancel(true); // 超时后取消任务
            }
        }
        
        // 4. 关闭线程池(关键步骤,避免线程泄漏)
        fixedThreadPool.shutdown();
        // 等待线程池关闭(最多等10秒)
        try {
            if (!fixedThreadPool.awaitTermination(10, TimeUnit.SECONDS)) {
                fixedThreadPool.shutdownNow(); // 强制关闭未执行的任务
            }
        } catch (InterruptedException e) {
            fixedThreadPool.shutdownNow();
        }
        System.out.println("线程池已关闭");
    }
}

关键注意点

  • 必须调shutdown()shutdownNow()关闭线程池,否则核心线程会一直存活,导致 JVM 无法退出。
  • submit()execute()更灵活,支持获取任务结果和超时控制,适合需要监控任务状态的场景。

源码解析:为什么会有 OOM 风险?

newFixedThreadPool的源码可知,其任务队列LinkedBlockingQueue的默认构造:

// LinkedBlockingQueue默认构造(无参)
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE); // 容量=2^31-1≈21亿
}

当任务提交速度远大于线程处理速度时(如每秒提交 1000 个任务,3 个线程每秒仅能处理 3 个),任务会在队列中持续积压。每个任务都是一个对象,会占用堆内存,当队列中任务数达到数百万甚至数千万时,会触
java.lang.OutOfMemoryError: Java heap space

实战案例:某电商项目newFixedThreadPool(5)处理订单支付回调,秒杀活动期间每秒产生 5000 个回调任务,线程每秒仅能处理 20 个,1 小时后队列积压 1700 万 + 任务,堆内存从 2G 暴涨到 8G,最终服务宕机。

替代方案:生产环境如何安全实现 “固定线程池”?

核心思路:用 ThreadPoolExecutor 手动配置,将任务队列改为有界队列,并指定合理的拒绝策略。示例:

// 安全的固定大小线程池(8核CPU场景)
ExecutorService safeFixedThreadPool = new ThreadPoolExecutor(
    5, // 核心线程数(根据业务调整)
    5, // 最大线程数=核心线程数(固定大小)
    0L, 
    TimeUnit.MILLISECONDS,
    new ArrayBlockingQueue<>(1000), // 有界队列,容量1000(避免积压)
    new CustomThreadFactory("order-callback"), // 自定义线程工厂(便于排查)
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略(调用者线程执行,减缓提交速度)
);

方式 2:newCachedThreadPool—— 可缓存的线程池

核心特性与适用场景

  • 线程动态伸缩:核心线程数 = 0,最大线程数 = Integer.MAX_VALUE(理论上可创建无限线程),线程空闲 60 秒后自动销毁。
  • 任务队列特殊:使SynchronousQueue(同步队列),不存储任务 —— 提交任务时必须有空闲线程接收,否则立即创建新线程。
  • 适用场景:任务执行时间短、任务量波动大的场景,如 “接口异步回调通知(单次执行 50ms)”“临时数据校验(每秒任务量 0-1000 波动)”。

完整用法示例(含线程回收监控)

public class CachedThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建可缓存线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        // 2. 模拟分3波提交任务(每波间隔70秒,观察线程回收)
        for (int wave = 1; wave <= 3; wave++) {
            System.out.printf("=== 第%d波任务开始提交 ===%n", wave);
            int taskCount = wave * 5; // 第1波5个任务,第2波10个,第3波15个
            for (int i = 0; i < taskCount; i++) {
                int taskId = (wave - 1) * 10 + i;
                cachedThreadPool.execute(() -> {
                    System.out.printf("[任务%d] 执行中,线程名:%s%n", 
                                     taskId, Thread.currentThread().getName());
                    try {
                        Thread.sleep(500); // 模拟短任务(500ms)
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
            }
            // 打印当前线程池状态(需通过反射获取,JDK无直接API)
            printThreadPoolStatus(cachedThreadPool, "第" + wave + "波任务提交后");
            // 间隔70秒(超过线程空闲60秒,观察线程回收)
            Thread.sleep(70000);
        }
        // 3. 关闭线程池
        cachedThreadPool.shutdown();
    }
    
    // 反射获取线程池状态(核心线程数、活跃线程数、队列任务数)
    private static void printThreadPoolStatus(ExecutorService executor, String label) {
        if (executor instanceof ThreadPoolExecutor) {
            ThreadPoolExecutor pool = (ThreadPoolExecutor) executor;
            System.out.printf("[%s] 核心线程数:%d,活跃线程数:%d,队列任务数:%d%n",
                             label, pool.getCorePoolSize(), 
                             pool.getActiveCount(), pool.getQueue().size());
        }
    }
}

执行结果关键观察

  • 第 1 波提交 5 个任务后,活跃线程数 = 5;70 秒后线程空闲超过 60 秒,活跃线程数降至 0(线程被回收)。
  • 第 2 波提交 10 个任务时,会重新创建 10 个线程,执行完后 70 秒再次回收,避免资源浪费。

源码解析:为什么会触发 “线程爆炸”?

newCachedThreadPool的核心参数配置如下:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

风险点集中在两点:

最大线程数无上限Integer.MAX_VALUE意味着理论上可创建 21 亿个线程,而每个线程默认占用 1M 栈内存(可通过-Xss调整),创建 1000 个线程就占用 1G 内存,创建 10 万个线程会直接导致 OOM。

同步队列无缓冲SynchronousQueue不存储任务,当没有空闲线程时,提交任务会立即创建新线程。若任务执行时间意外变长(如原本 50ms 的任务因接口超时变成 5 秒),会导致线程数暴增。

实战案例:某 API 网关newCachedThreadPool处理请求日志异步打印,正常情况下日志打印耗时 10ms,线程数稳定在 10 左右。某天日志服务故障,打印耗时增至 3 秒,1 分钟内线程数从 10 飙升到 12000,导致 CPU 使用率达 100%(上下文切换频繁),网关无法处理正常请求。

替代方案:生产环境如何安全实现 “可缓存线程池”?

核心思路:限制最大线程数,避免线程无限创建,同时保留 “线程自动回收” 的特性。示例:

// 安全的可缓存线程池(8核CPU场景)
ExecutorService safeCachedThreadPool = new ThreadPoolExecutor(
    0, // 核心线程数=0(无长期存活线程)
    50, // 最大线程数=50(限制线程上限,避免爆炸)
    60L, 
    TimeUnit.SECONDS,
    new SynchronousQueue<>(),
    new CustomThreadFactory("api-log"),
    new ThreadPoolExecutor.DiscardOldestPolicy() // 拒绝策略(丢弃旧任务,保留新任务)
);

方式 3:newSingleThreadExecutor—— 单线程线程池

核心特性与适用场景

  • 单线程串行执行:核心线程数 = 1,最大线程数 = 1,所有任务按提交顺序(FIFO)由同一个线程执行。
  • 线程故障自动恢复:若唯一的线程因异常终止,会自动创建一个新线程替代,保证任务不中断。
  • 任务队列无界:同样使LinkedBlockingQueue(默认容量 21 亿),避免任务被拒绝。
  • 适用场景:需要串行执行的任务,如 “数据库表数据同步(避免并发修改主键冲突)”“日志文件写入(避免多线程写文件错乱)”“定时任务调度(确保任务按顺序执行)”。

完整用法示例(含线程恢复验证)

public class SingleThreadExecutorDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建单线程线程池
        ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
        
        // 2. 提交5个任务,第3个任务故意抛出异常(验证线程恢复)
        for (int i = 0; i < 5; i++) {
            int taskId = i;
            singleThreadPool.submit(() -> {
                String threadName = Thread.currentThread().getName();
                System.out.printf("[任务%d] 开始执行,线程名:%s%n", taskId, threadName);
                try {
                    if (taskId == 2) {
                        // 第3个任务故意抛出异常
                        throw new RuntimeException("任务执行异常(模拟故障)");
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.out.printf("[任务%d] 被中断,线程名:%s%n", taskId, threadName);
                } catch (RuntimeException e) {
                    System.out.printf("[任务%d] 执行异常,线程名:%s,原因:%s%n", 
                                     taskId, threadName, e.getMessage());
                    // 异常无需捕获,线程池会自动处理并创建新线程
                }
                System.out.printf("[任务%d] 执行完成,线程名:%s%n", taskId, threadName);
            });
            Thread.sleep(500); // 间隔提交,便于观察顺序
        }
        
        // 3. 关闭线程池
        singleThreadPool.shutdown();
        singleThreadPool.awaitTermination(10, TimeUnit.SECONDS);
        System.out.println("线程池已关闭");
    }
}

执行结果关键观察

  • 任务 0、1 由 “pool-1-thread-1” 执行;
  • 任务 2 抛出异常后,“pool-1-thread-1” 终止;
  • 任务 3、4 自动由新创建的 “pool-1-thread-2” 执行,实现故障恢复。

源码解析:风险与 “伪单线程” 问题

newSingleThreadExecutor的源码如下:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(
        new ThreadPoolExecutor(1, 1,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>())
    );
}

(1)核心风险:无界队列导致 OOM

newFixedThreadPool一致,任务队列LinkedBlockingQueue(无界),当任务提交速度超过单线程处理速度时,队列会持续积压,最终触发 OOM。

(2)隐藏问题:无法强制修改线程数(伪单线程)


FinalizableDelegatedExecutorServic
e是一个包装类,它屏蔽ThreadPoolExecutorsetCorePoolSize()setMaximumPoolSize()等方法,意味着一旦创建,无法通过代码动态调整线程数。若业务场景需要临时增加线程数(如紧急数据同步),只能重新创建线程池,灵活性差。

替代方案:生产环境如何安全实现 “单线程线程池”?

核心思路:手动创建 ThreadPoolExecutor,使用有界队列,保留动态调整线程数的能力。示例:

// 安全的单线程线程池
ThreadPoolExecutor safeSingleThreadPool = new ThreadPoolExecutor(
    1, // 核心线程数=1
    1, // 最大线程数=1(默认单线程)
    0L, 
    TimeUnit.MILLISECONDS,
    new ArrayBlockingQueue<>(500), // 有界队列,容量500
    new CustomThreadFactory ("data-sync"), // 自定义线程工厂
new ThreadPoolExecutor.AbortPolicy () // 拒绝策略(数据同步任务重要,抛异常提醒)
);
// 如需临时调整线程数(如紧急同步),可动态修改
safeSingleThreadPool.setMaximumPoolSize (3);

方式4:newScheduledThreadPool——支持定时/周期性任务的线程池

核心特性与适用场景

  • 线程数动态调整:核心线程数可手动指定(固定),最大线程数= `Integer.MAX_VALUE`(理论无上限),非核心线程空闲后立即销毁(无存活时间配置)。
  • 任务队列特殊:使用`DelayedWorkQueue`(延迟队列),任务按“执行时间”排序,只有到达指定时间才会被线程取出执行。

支持3类定时任务:

  • 延迟执行:任务提交后,延迟N时间再执行1次;
  • 固定延迟执行:任务执行完后,间隔N时间再执行下一次(以上次任务结束时间为起点);
  • 固定频率执行:任务开始执行后,间隔N时间再执行下一次(以上次任务开始时间为起点)。

适用场景:需定时执行的任务,如“每天凌晨2点执行数据库备份”“每5分钟检查服务健康状态”“订单创建后30分钟未支付自动取消”。

完整用法示例(含3类定时任务)

public class ScheduledThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建核心线程数为2的定时线程池
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
        
        // 2. 任务1:延迟执行(提交后3秒执行1次)
        ScheduledFuture<?> delayTask = scheduledThreadPool.schedule(() -> {
            System.out.printf("[延迟任务] 执行时间:%s,线程名:%s%n", 
                             new SimpleDateFormat("HH:mm:ss").format(new Date()),
                             Thread.currentThread().getName());
        }, 3, TimeUnit.SECONDS);
        
        // 3. 任务2:固定延迟执行(首次延迟1秒,之后间隔2秒执行,以上次结束时间为起点)
        ScheduledFuture<?> fixedDelayTask = scheduledThreadPool.scheduleWithFixedDelay(() -> {
            long start = System.currentTimeMillis();
            System.out.printf("[固定延迟任务] 开始时间:%s,线程名:%s%n", 
                             new SimpleDateFormat("HH:mm:ss").format(new Date()),
                             Thread.currentThread().getName());
            try {
                Thread.sleep(1000); // 模拟任务耗时1秒
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            long end = System.currentTimeMillis();
            System.out.printf("[固定延迟任务] 结束时间:%s,耗时:%dms%n", 
                             new SimpleDateFormat("HH:mm:ss").format(new Date()),
                             (end - start));
        }, 1, 2, TimeUnit.SECONDS);
        
        // 4. 任务3:固定频率执行(首次延迟1秒,之后间隔2秒执行,以上次开始时间为起点)
        ScheduledFuture<?> fixedRateTask = scheduledThreadPool.scheduleAtFixedRate(() -> {
            long start = System.currentTimeMillis();
            System.out.printf("[固定频率任务] 开始时间:%s,线程名:%s%n", 
                             new SimpleDateFormat("HH:mm:ss").format(new Date()),
                             Thread.currentThread().getName());
            try {
                Thread.sleep(1000); // 模拟任务耗时1秒
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            long end = System.currentTimeMillis();
            System.out.printf("[固定频率任务] 结束时间:%s,耗时:%dms%n", 
                             new SimpleDateFormat("HH:mm:ss").format(new Date()),
                             (end - start));
        }, 1, 2, TimeUnit.SECONDS);
        
        // 5. 运行5秒后,取消固定频率任务(演示任务取消)
        Thread.sleep(5000);
        if (!fixedRateTask.isCancelled()) {
            fixedRateTask.cancel(true); // true:中断正在执行的任务
            System.out.println("[固定频率任务] 已取消");
        }
        
        // 6. 避免主线程退出,让任务继续执行(实际项目中需根据业务控制线程池生命周期)
        Thread.sleep(10000);
        // 7. 关闭线程池(定时线程池需手动关闭,否则会一直运行)
        scheduledThreadPool.shutdown();
        if (!scheduledThreadPool.awaitTermination(5, TimeUnit.SECONDS)) {
            scheduledThreadPool.shutdownNow();
        }
        System.out.println("线程池已关闭");
    }
}

执行结果关键对比(固定延迟 vs 固定频率):

  • 固定延迟任务:首次 1 秒后开始(如 10:00:01),耗时 1 秒(10:00:02 结束),下次执行时间为 10:00:04(结束时间 + 2 秒);
  • 固定频率任务:首次 1 秒后开始(10:00:01),耗时 1 秒(10:00:02 结束),下次执行时间为 10:00:03(开始时间 + 2 秒)。

源码解析:风险与 “任务堆积” 问题

newScheduledThreadPool的源码如下:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

// ScheduledThreadPoolExecutor的父类是ThreadPoolExecutor,其构造默认参数:
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

(1)核心风险:最大线程数无上限导致 OOM

与newCachedThreadPool类似,最大线程数 = Integer.MAX_VALUE,若定时任务执行时间过长(如任务间隔 2 秒,但执行耗时 3 秒),会导致线程数持续增加:

  • 第 1 个任务 10:00:01 开始,10:00:04 结束(耗时 3 秒);
  • 第 2 个任务本应 10:00:03 开始,但此时线程被占用,会创建新线程执行;
  • 长期累积会导致线程数爆炸,触发 OOM 或 CPU 使用率飙升。

(2)隐藏问题:延迟队列的 “任务堆积”

DelayedWorkQueue的默认容量是Integer.MAX_VALUE,若大量定时任务被提交(如每秒提交 1000 个 “延迟 1 小时执行” 的任务),会导致队列中堆积大量未到执行时间的任务,占用大量堆内存,最终触发 OOM。

实战案例:某电商项目用newScheduledThreadPool(5)处理 “订单 30 分钟未支付自动取消”,大促期间每秒产生 5000 个新订单,每个订单都提交一个延迟任务,1 小时后队列堆积 1800 万 + 任务,堆内存耗尽,服务宕机。

替代方案:生产环境如何安全实现 “定时线程池”?

核心思路:限制最大线程数 + 控制延迟队列容量 + 任务合并,避免资源耗尽。示例:

// 安全的定时线程池(8核CPU场景)
ScheduledExecutorService safeScheduledThreadPool = new ScheduledThreadPoolExecutor(
    5, // 核心线程数=5(根据定时任务数量调整)
    new CustomThreadFactory("order-timeout"), // 自定义线程工厂
    new ThreadPoolExecutor.AbortPolicy() // 拒绝策略(订单任务重要,抛异常告警)
) {
    // 重写DelayedWorkQueue的构造,限制队列容量(默认无界,需自定义)
    @Override
    protected BlockingQueue<Runnable> createQueue() {
        // 自定义延迟队列,容量限制为10000(避免堆积)
        return new LimitedDelayedWorkQueue(10000);
    }
};

// 自定义有界延迟队列(继承DelayedWorkQueue,重写offer方法限制容量)
class LimitedDelayedWorkQueue extends DelayedWorkQueue {
    private final int capacity;

    public LimitedDelayedWorkQueue(int capacity) {
        this.capacity = capacity;
    }

    @Override
    public boolean offer(Runnable e) {
        // 队列满时返回false,触发拒绝策略
        if (size() >= capacity) {
            return false;
        }
        return super.offer(e);
    }
}

// 额外优化:订单超时任务合并(同一用户的未支付订单,可合并为1个延迟任务)
// 避免重复提交,减少队列任务数

总结

通过对 4 种线程池的深度解析,我们可以明确 Executors 工具类的使用边界:

1. 可以使用 Executors 的场景

  • Demo 开发 / 本地测试:无需关注性能和资源风险,快速验证业务逻辑(如验证多线程任务执行顺序);
  • 短期一次性任务:任务量少、执行时间短,不会出现任务堆积(如数据导出脚本,执行完就关闭线程池);
  • 非核心非生产环境:如内部管理系统的日志处理、数据统计,即使出现 OOM 也不会影响核心业务。

2. 必须禁用 Executors 的场景

  • 生产环境核心业务:如订单处理、支付回调、用户数据同步,需避免资源耗尽风险;
  • 高并发场景:任务量波动大、执行时间不确定(如秒杀、大促),易触发线程爆炸或 OOM;
  • 长期运行的服务:如网关、微服务实例,线程池会持续接收任务,需严格控制资源占用。

3. 生产环境的 “黄金法则”

无论使用哪种线程池,记住 3 个核心原则:

  1. 拒绝无界队列:任务队列必须指定容量,避免任务堆积导致 OOM;
  2. 限制最大线程数:根据 CPU 核心数和任务类型,设置合理的最大线程数(如 IO 密集型 = 2*CPU 核心数,CPU 密集型 = CPU 核心数 + 1);
  3. 自定义线程工厂:给线程起有意义的名字,便于日志排查(如 “order-callback-thread-1”)。

最后,用一张表格汇总 4 种线程池的核心信息,方便快速查阅:

线程池类型

核心参数特点

核心风险

适用场景

生产替代方案

newFixedThreadPool

核心线程数 = 最大线程数,无界队列

任务堆积→OOM

任务量已知、并发固定的短期任务

ThreadPoolExecutor + 有界队列 + 固定线程数

newCachedThreadPool

核心线程数 = 0,最大线程数无上限

线程爆炸→OOM/CPU 100%

任务短、量波动大的临时任务

ThreadPoolExecutor + 限制最大线程数

newSingleThreadExecutor

单线程,无界队列,线程自动恢复

任务堆积→OOM,无法调线程数

需串行执行的低并发任务

ThreadPoolExecutor + 有界队列 + 动态线程数

newScheduledThreadPool

核心线程数固定,最大线程数无上限

线程爆炸 + 队列堆积→OOM

简单定时任务(非核心)


ScheduledThreadPoolExecutor + 有界延迟队列 + 任务合并

掌握这些知识,你就能在实际开发中避开 Executors 的 “坑”,用更安全、更灵活的方式管理线程池,保障系统的高性能和稳定性。

相关推荐

oracle数据导入导出_oracle数据导入导出工具

关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...

继续学习Python中的while true/break语句

上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个else解...

python continue和break的区别_python中break语句和continue语句的区别

python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...

简单学Python——关键字6——break和continue

Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...

2-1,0基础学Python之 break退出循环、 continue继续循环 多重循

用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...

Python 中 break 和 continue 傻傻分不清

大家好啊,我是大田。今天分享一下break和continue在代码中的执行效果是什么,进一步区分出二者的区别。一、continue例1:当小明3岁时不打印年龄,其余年龄正常循环打印。可以看...

python中的流程控制语句:continue、break 和 return使用方法

Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...

L017:continue和break - 教程文案

continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...

作为前端开发者,你都经历过怎样的面试?

已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...

面试被问 const 是否不可变?这样回答才显功底

作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...

2023金九银十必看前端面试题!2w字精品!

导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。答案:CSS的盒模型是用于布局和定位元素的概念。它由内容区域...

前端面试总结_前端面试题整理

记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...

由浅入深,66条JavaScript面试知识点(七)

作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录由浅入深,66条JavaScript面试知识点(一)由浅入深,66...

2024前端面试真题之—VUE篇_前端面试题vue2020及答案

添加图片注释,不超过140字(可选)1.vue的生命周期有哪些及每个生命周期做了什么?beforeCreate是newVue()之后触发的第一个钩子,在当前阶段data、methods、com...

今年最常见的前端面试题,你会做几道?

在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...