本文详细解析了Java中创建线程池的五种方法:Executors工厂类提供的四种预设线程池(CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor)以及通过ThreadPoolExecutor的自定义创建方式。文章深入剖析了各方法的实现原理、参数配置及适用场景,并通过源码分析揭示了Executors预设方法的潜在风险——可能导致内存溢出或线程资源耗尽。最后,结合阿里巴巴开发规范,强调在生产环境中应优先使用ThreadPoolExecutor进行精细化配置,确保线程池行为可控、资源安全。

博主博客

目录

概述

Java中创建线程池主要分为两类方法:

  1. 通过Executors工厂类:提供4种预设线程池配置
  2. 通过ThreadPoolExecutor类:完全自定义线程池参数

一、Executors工厂类提供的四种方法

1. newCachedThreadPool - 可缓存线程池

创建一个可根据需要创建新线程的线程池,空闲线程默认保留60秒。

特点

  • 线程数无上限(理论最大Integer.MAX_VALUE)
  • 空闲线程超时自动回收
  • 适合执行大量短期异步任务
private static void createCachedThreadPool() {
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        executorService.execute(() -> {
            System.out.println(new Date() + " " + Thread.currentThread().getName() + " " + index);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

执行效果:每个任务可能由不同线程执行,线程会复用但也会不断新建。

2. newFixedThreadPool - 固定大小线程池

创建固定线程数量的线程池,超出的任务在队列中等待。

特点

  • 线程数量固定
  • 无界任务队列(LinkedBlockingQueue)
  • 适合负载较重的服务器场景
private static void createFixedThreadPool() {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i++) {
        final int index = i;
        executorService.execute(() -> {
            System.out.println(new Date() + " " + Thread.currentThread().getName() + " " + index);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

执行效果:最多只有3个线程工作,任务按提交顺序执行。

3. newScheduledThreadPool - 周期性任务线程池

创建支持定时及周期性任务执行的线程池。

特点

  • 支持延迟执行和定期执行
  • 适合定时任务、周期任务场景
private static void createScheduledThreadPool() {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
    System.out.println(new Date() + " 提交任务");
    for (int i = 0; i < 10; i++) {
        final int index = i;
        executorService.schedule(() -> {
            System.out.println(new Date() + " " + Thread.currentThread().getName() + " " + index);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 3, TimeUnit.SECONDS);
    }
}

执行效果:任务延迟3秒后开始执行,按固定频率调度。

4. newSingleThreadExecutor - 单线程线程池

创建只有一个工作线程的线程池,保证任务按顺序执行。

特点

  • 单工作线程
  • 无界任务队列
  • 保证任务执行顺序
private static void createSingleThreadPool() {
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        executorService.execute(() -> {
            System.out.println(new Date() + " " + Thread.currentThread().getName() + " " + index);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

执行效果:所有任务由同一个线程按顺序执行。

二、ThreadPoolExecutor自定义创建

构造方法详解

public ThreadPoolExecutor(int corePoolSize,
                         int maximumPoolSize,
                         long keepAliveTime,
                         TimeUnit unit,
                         BlockingQueue<Runnable> workQueue,
                         ThreadFactory threadFactory,
                         RejectedExecutionHandler handler)

1. 七大参数说明

参数名 说明 建议配置
corePoolSize 核心线程数,线程池长期保持的线程数量 CPU密集型:N+1
IO密集型:2N+1
maximumPoolSize 最大线程数,线程池允许的最大线程数量 根据系统负载和资源决定
keepAliveTime 空闲线程存活时间,非核心线程空闲时的存活时间 根据任务特性设置,通常60s
unit 时间单位 TimeUnit.SECONDS/MILLISECONDS等
workQueue 任务队列,存储等待执行的任务 根据需求选择不同类型的阻塞队列
threadFactory 线程工厂,用于创建新线程 可自定义线程名称、优先级等
handler 拒绝策略,当线程池无法处理新任务时的策略 根据业务需求选择

2. 工作队列类型

队列类型 特点 适用场景
ArrayBlockingQueue 有界数组队列,FIFO 需要控制队列大小,防止资源耗尽
LinkedBlockingQueue 有界/无界链表队列 默认无界,可能引起OOM
SynchronousQueue 不存储元素,直接传递 高吞吐量,不缓冲任务
PriorityBlockingQueue 优先级队列 需要按优先级执行任务
DelayedWorkQueue 延迟队列 定时任务场景

3. 拒绝策略

策略 行为 适用场景
AbortPolicy(默认) 抛出RejectedExecutionException 需要明确知道任务被拒绝
CallerRunsPolicy 由调用线程执行该任务 不希望丢弃任务,可接受降级
DiscardOldestPolicy 丢弃队列最旧的任务,执行当前任务 允许丢弃旧任务
DiscardPolicy 直接丢弃任务,不抛异常 允许静默丢弃

4. 线程池执行规则

  1. 创建核心线程:当任务数 < corePoolSize时,创建新线程
  2. 放入任务队列:当任务数 >= corePoolSize且队列未满时,任务入队
  3. 创建非核心线程:当队列已满且任务数 < maximumPoolSize时,创建新线程
  4. 执行拒绝策略:当队列已满且任务数 = maximumPoolSize时,执行拒绝策略

5. 自定义线程池示例

private static void createCustomThreadPool() {
    // 创建自定义线程池
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
        2,                      // 核心线程数
        10,                     // 最大线程数
        1, TimeUnit.MINUTES,    // 空闲线程存活时间
        new ArrayBlockingQueue<>(5),  // 有界队列,容量5
        Executors.defaultThreadFactory(),  // 默认线程工厂
        new ThreadPoolExecutor.AbortPolicy()  // 拒绝策略
    );
    
    // 提交任务
    for (int i = 0; i < 15; i++) {
        final int index = i;
        try {
            executor.execute(() -> {
                System.out.println(new Date() + " " + 
                    Thread.currentThread().getName() + " 执行任务" + index);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        } catch (RejectedExecutionException e) {
            System.out.println("任务" + index + "被拒绝:" + e.getMessage());
        }
    }
    
    // 优雅关闭线程池
    executor.shutdown();
    try {
        if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
            executor.shutdownNow();
        }
    } catch (InterruptedException e) {
        executor.shutdownNow();
    }
}

三、五种方法的实现原理与比较

1. 源码分析

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

// newFixedThreadPool实现  
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

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

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

2. 五种方法对比

特性 CachedThreadPool FixedThreadPool SingleThreadExecutor ScheduledThreadPool ThreadPoolExecutor
核心线程数 0 固定值 1 固定值 自定义
最大线程数 Integer.MAX_VALUE 同核心线程数 1 Integer.MAX_VALUE 自定义
线程存活时间 60秒 0(永不过期) 0(永不过期) 0(永不过期) 自定义
工作队列 SynchronousQueue LinkedBlockingQueue LinkedBlockingQueue DelayedWorkQueue 自定义
队列边界 无容量 无界 无界 无界 可自定义有界
适用场景 短时异步任务 长期稳定负载 顺序执行任务 定时/周期任务 所有场景
风险 可能创建大量线程导致OOM 可能队列堆积导致OOM 可能队列堆积导致OOM 可能创建大量线程导致OOM 可控风险

3. 阿里巴巴开发规范建议

【强制】 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors返回的线程池对象的弊端:

  1. FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
  2. CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM

四、线程池的最佳实践建议

1. 参数配置建议

// CPU密集型任务(计算型)
int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;

// IO密集型任务(网络/磁盘IO)
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;

// 实际项目中推荐配置方式
public class ThreadPoolConfig {
    public static ThreadPoolExecutor createCustomExecutor() {
        int corePoolSize = 5;
        int maxPoolSize = 20;
        int queueCapacity = 100;
        long keepAliveTime = 60L;
        
        return new ThreadPoolExecutor(
            corePoolSize,
            maxPoolSize,
            keepAliveTime,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(queueCapacity),
            new CustomThreadFactory("business-pool"), // 自定义线程工厂
            new ThreadPoolExecutor.CallerRunsPolicy() // 降级策略
        );
    }
}

2. 线程工厂自定义

public class CustomThreadFactory implements ThreadFactory {
    private final String namePrefix;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    
    public CustomThreadFactory(String poolName) {
        namePrefix = poolName + "-thread-";
    }
    
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r, namePrefix + threadNumber.getAndIncrement());
        // 设置线程属性
        thread.setDaemon(false);
        thread.setPriority(Thread.NORM_PRIORITY);
        // 设置异常处理器
        thread.setUncaughtExceptionHandler((t, e) -> {
            System.err.println("线程" + t.getName() + "发生异常: " + e.getMessage());
        });
        return thread;
    }
}

3. 监控和调试

public class ThreadPoolMonitor {
    public static void printThreadPoolStatus(ThreadPoolExecutor executor) {
        System.out.println("=== 线程池状态 ===");
        System.out.println("核心线程数: " + executor.getCorePoolSize());
        System.out.println("活动线程数: " + executor.getActiveCount());
        System.out.println("最大线程数: " + executor.getMaximumPoolSize());
        System.out.println("任务总数: " + executor.getTaskCount());
        System.out.println("已完成任务数: " + executor.getCompletedTaskCount());
        System.out.println("队列大小: " + executor.getQueue().size());
        System.out.println("队列剩余容量: " + executor.getQueue().remainingCapacity());
    }
}

总结

关键要点

  1. Executors工厂方法:适合快速原型开发,但生产环境需谨慎使用
  2. ThreadPoolExecutor自定义:生产环境推荐方式,可精确控制资源使用
  3. 参数配置原则:根据任务类型(CPU/IO密集型)合理配置线程数
  4. 队列选择:有界队列可防止内存溢出,无界队列需评估风险
  5. 拒绝策略:根据业务容忍度选择合适的拒绝策略

选择建议

  • 简单测试/学习:可使用Executors快速创建
  • 生产环境:必须使用ThreadPoolExecutor自定义配置
  • 定时任务:优先使用ScheduledThreadPoolExecutor
  • 高并发场景:使用有界队列+合适的拒绝策略

补充说明

在实际项目中,通常使用Spring的@Async注解或配置ThreadPoolTaskExecutor来管理线程池,这些框架底层也是基于ThreadPoolExecutor的实现,但提供了更方便的配置和管理方式。

记住:合理的线程池配置需要结合具体业务场景、系统资源和性能需求进行调优,没有一成不变的"最佳配置"。

参考文献