为什么禁止用Executors创建线程池?💣

85 阅读3分钟

阿里Java开发手册明确规定:禁止使用Executors创建线程池!为什么?因为它是OOM的定时炸弹!

一、Executors的三大陷阱

陷阱1:newFixedThreadPool - 无界队列OOM

// ❌ 危险代码
ExecutorService executor = Executors.newFixedThreadPool(10);

源码:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
        nThreads, nThreads,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>()  // 💣 无界队列!
    );
}

问题: LinkedBlockingQueue默认容量Integer.MAX_VALUE(21亿)

场景:

ExecutorService executor = Executors.newFixedThreadPool(10);

// 疯狂提交任务
for (int i = 0; i < 1000000; i++) {
    executor.submit(() -> {
        Thread.sleep(10000); // 慢任务
    });
}
// 队列积压100万个任务 → OOM!💥

陷阱2:newCachedThreadPool - 无限线程OOM

// ❌ 危险代码
ExecutorService executor = Executors.newCachedThreadPool();

源码:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(
        0, Integer.MAX_VALUE,  // 💣 最大线程数无限!
        60L, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>()
    );
}

问题: 最大线程数Integer.MAX_VALUE

场景:

ExecutorService executor = Executors.newCachedThreadPool();

// 突发大量任务
for (int i = 0; i < 10000; i++) {
    executor.submit(() -> {
        Thread.sleep(10000);
    });
}
// 创建1万个线程 → OOM!💥

陷阱3:newScheduledThreadPool - 无界队列OOM

// ❌ 危险代码
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);

源码:

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());  // 💣 无界队列!
}

二、正确的创建方式✅

方式1:手动创建ThreadPoolExecutor(推荐)

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    10,                              // 核心线程数
    50,                              // 最大线程数
    60L, TimeUnit.SECONDS,           // 空闲线程存活时间
    new ArrayBlockingQueue<>(1000),  // 有界队列,容量1000
    new ThreadFactoryBuilder()
        .setNameFormat("业务线程池-%d")
        .setDaemon(false)
        .build(),
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

方式2:Spring Boot配置

@Configuration
public class ThreadPoolConfig {
    
    @Bean("asyncExecutor")
    public ThreadPoolTaskExecutor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(1000);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("async-");
        
        executor.setRejectedExecutionHandler(
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
        
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        
        executor.initialize();
        return executor;
    }
}

方式3:使用Guava ThreadFactoryBuilder

ThreadFactory threadFactory = new ThreadFactoryBuilder()
    .setNameFormat("worker-%d")
    .setDaemon(true)
    .setPriority(Thread.NORM_PRIORITY)
    .setUncaughtExceptionHandler((t, e) -> 
        log.error("线程" + t.getName() + "异常", e)
    )
    .build();

ExecutorService executor = new ThreadPoolExecutor(
    10, 20, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100),
    threadFactory,
    new ThreadPoolExecutor.AbortPolicy()
);

三、参数如何设置?

CPU密集型任务

int processors = Runtime.getRuntime().availableProcessors();
int corePoolSize = processors + 1;
int maxPoolSize = processors * 2;

IO密集型任务

int processors = Runtime.getRuntime().availableProcessors();
int corePoolSize = processors * 2;
int maxPoolSize = processors * 4;

混合型任务

// 根据IO等待时间比例调整
int corePoolSize = processors * (1 + IO等待时间/CPU计算时间);

四、队列选择

队列类型容量特点适用场景
ArrayBlockingQueue有界数组实现内存可控 ✅
LinkedBlockingQueue可选链表实现需要指定容量
SynchronousQueue0直接交付CachedThreadPool
PriorityBlockingQueue无界优先级任务有优先级
DelayQueue无界延迟定时任务

五、监控与告警

// 定时监控线程池状态
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();

monitor.scheduleAtFixedRate(() -> {
    ThreadPoolExecutor tpe = (ThreadPoolExecutor) executor;
    
    int activeCount = tpe.getActiveCount();
    int poolSize = tpe.getPoolSize();
    int queueSize = tpe.getQueue().size();
    long completedTasks = tpe.getCompletedTaskCount();
    
    log.info("线程池状态: 活跃={}, 总数={}, 队列={}, 已完成={}",
        activeCount, poolSize, queueSize, completedTasks);
    
    // 告警
    if (queueSize > 800) {
        alert("队列堆积: " + queueSize);
    }
    
    if (activeCount == poolSize && queueSize > 500) {
        alert("线程池接近满负荷");
    }
    
}, 10, 10, TimeUnit.SECONDS);

六、面试高频问答💯

Q: 为什么禁止用Executors?

A: 三大风险:

  1. newFixedThreadPool:无界队列,任务堆积OOM
  2. newCachedThreadPool:无限线程,线程爆炸OOM
  3. newScheduledThreadPool:无界队列,任务堆积OOM

Q: 线程池核心参数如何设置?

A:

  • corePoolSize:CPU密集=核心数+1,IO密集=核心数*2
  • maxPoolSize:CPU密集=核心数2,IO密集=核心数4
  • queueCapacity:根据内存和业务设置(建议1000以内)
  • keepAliveTime:60秒(根据业务调整)

Q: 如何选择队列?

A:

  • 有界队列:ArrayBlockingQueue(推荐)
  • 无界队列:禁止使用(OOM风险)
  • 零容量:SynchronousQueue(特殊场景)

下一篇→ 活锁:比死锁更隐蔽的陷阱🔄