线程池-ThreadPoolExecutor

ThreadPoolExecutor

ThreadPoolExecutor 是 Java 并发包 (java.util.concurrent) 中的一个重要类,它提供了一个线程池的实现,用于高效地管理和复用线程,执行异步任务。

核心概念

ThreadPoolExecutor 通过线程池技术解决了频繁创建和销毁线程带来的性能开销问题。它维护一组工作线程,当有任务提交时,从池中分配线程执行任务,任务完成后线程返回池中等待下一个任务。

线程池状态和线程数量是用同一个变量保存的,类似读写锁的 state 高低位表示读写锁,目的是避免多个变量要多次CAS

主要构造参数

ThreadPoolExecutor 有多个构造函数,最完整的构造函数包含以下核心参数:

public ThreadPoolExecutor(int corePoolSize,
                         int maximumPoolSize,
                         long keepAliveTime,
                         TimeUnit unit,
                         BlockingQueue<Runnable> workQueue,
                         ThreadFactory threadFactory,
                         RejectedExecutionHandler handler)
  1. corePoolSize - 核心线程数,即使空闲也会保留的线程数量
  2. maximumPoolSize - 最大线程数,允许创建的最大线程数量
  3. keepAliveTime - 非核心线程空闲时的存活时间
  4. unit - keepAliveTime 的时间单位
  5. workQueue - 用于保存等待执行的任务的阻塞队列
  6. threadFactory - 用于创建新线程的工厂(可以指定线程名称,方便定位bug)
  7. handler - 当线程池和队列都饱和时的拒绝策略

常用队列类型

  • LinkedBlockingQueue - 无界队列(如果不指定容量)
  • ArrayBlockingQueue - 有界队列
  • SynchronousQueue - 不存储元素的队列,每个插入操作必须等待另一个线程的移除操作
  • PriorityBlockingQueue - 具有优先级的无界队列

拒绝策略

当线程池和队列都饱和时,会触发拒绝策略:

  1. AbortPolicy - 默认策略,抛出RejectedExecutionException
  2. CallerRunsPolicy - 由提交任务的线程直接执行该任务(别忘了主线程)
  3. DiscardPolicy - 直接丢弃任务,不做任何处理
  4. DiscardOldestPolicy - 丢弃队列中最旧的任务,然后尝试重新提交当前任务

工作流程

  1. 线程池刚创建时,内部没有线程

  2. 当提交新任务时:

    • 如果当前线程数 < corePoolSize,创建新线程执行任务
    • 如果当前线程数 ≥ corePoolSize,将任务放入工作队列
    • 如果队列已满且线程数 < maximumPoolSize,创建新线程执行任务
    • 如果队列已满且线程数 = maximumPoolSize,执行拒绝策略
  3. 当线程完成任务后:

    • 它会从队列中获取下一个任务执行
    • 如果无任务可执行且线程数 > corePoolSize,超过keepAliveTime后线程终止
  4. 当线程数量达到最大数量,队列也满时,执行拒绝策略

常用方法

// 执行 Runnable 任务(无返回值)
void execute(Runnable command);

// 提交 Callable 任务,返回 Future 可用于获取任务执行结果或取消任务
<T> Future<T> submit(Callable<T> task);

// 提交 Runnable 任务,返回 Future 可用于等待任务完成(get()返回null)或取消任务
Future<?> submit(Runnable task);

// 批量提交任务,返回所有任务的Future列表(按任务集合迭代器顺序)
// 所有任务完成后才返回,若任何任务抛出异常,对应 Future 的 get() 会抛出异常
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;

// 批量提交任务,带超时时间,超时后未完成的任务会被取消
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;

// 批量提交任务,返回第一个成功完成的任务结果(其他未完成的任务会被取消)
// 如果所有任务都失败,抛出 ExecutionException
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;

// 批量提交任务,带超时时间,返回第一个成功完成的任务结果
// 超时后未完成的任务会被取消,若超时前没有任务完成则抛出 TimeoutException
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

/*
 * 关闭线程池
 * 1)线程池状态改为 SHUTDOWN
 * 2)优雅关闭:线程池不再接受新任务,但会继续执行已提交的任务(包括队列中的任务)
 * 3)不强制中断:不会主动中断正在运行的线程,而是等待它们自然完成
 */
void shutdown();

/* 关闭线程池
 * 1)线程池状态改为 STOP
 * 2)立即关闭:线程池不再接受新任务,并尝试中断所有正在执行的任务
 * 3)清空任务队列:返回尚未执行的 Runnable 任务列表(List<Runnable>)
 * 4)不保证所有任务停止:如果任务不检查中断状态(如未处理 InterruptedException),可能继续执行
 */
List<Runnable> shutdownNow();

shutdownNow() 响应中断示例

ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> {
    try {
        Thread.sleep(1000);
        System.out.println("Task 1 completed"); // 可能被中断
    } catch (InterruptedException e) {
        System.out.println("Task 1 interrupted");
        Thread.currentThread().interrupt();
    }
});
executor.submit(() -> System.out.println("Task 2 completed"));

List<Runnable> notExecutedTasks = executor.shutdownNow(); // 尝试中断所有任务
System.out.println("Cancelled tasks: " + notExecutedTasks.size());

优雅停止线程池示例

/**
 * 优雅关闭线程池的通用方法
 * @param executor 要关闭的线程池
 * @param poolName 线程池名称(用于日志)
 */
public static void shutdownThreadPool(ExecutorService executor, String poolName) {
    if (executor == null) {
        return;
    }

    try {
        // 第一步: 停止接收新任务
        executor.shutdown();
        System.out.println(poolName + ": 开始关闭,等待正在执行的任务完成...");

        // 第二步: 等待一段时间让现有任务完成
        if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
            // 第三步: 如果超时,取消所有正在执行的任务
            System.out.println(poolName + ": 等待超时,尝试强制关闭...");
            List<Runnable> unfinishedTasks = executor.shutdownNow();
            System.out.println(poolName + ": 已取消 " + unfinishedTasks.size() + " 个排队中的任务");
            
            // 第四步: 再次等待一段时间
            if (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
                System.err.println(poolName + ": 线程池未能正常终止!");
            }
        } else {
            System.out.println(poolName + ": 所有任务已完成");
        }
    } catch (InterruptedException e) {
        // 如果等待过程中线程被中断,立即取消所有任务
        System.err.println(poolName + ": 关闭过程被中断!立即强制关闭...");
        executor.shutdownNow();
        // 恢复中断状态
        Thread.currentThread().interrupt();
    } finally {
        System.out.println(poolName + ": 关闭流程完成");
    }
}

Executors

newFixedThreadPool

  • 固定线程数量,大小核心线程数量=最大线程数量(既然没有救急线程,存活时间也就没意义了,就是 0)
  • 使用无界队列 LinkedBlockingQueue(没指定大小就是 Integer 最大值,常认为就是无界的)
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

newCachedThreadPool

  • 线程全都是救急线程,核心线程数时0,最大线程数量无限(Integer 最大值)
  • 存活时间是 60 秒
  • 使用 SynchronousQueue 队列(SynchronousQueue 容量是 0,当有线程取数据时,才能入队成功)
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

newSingleThreadExecutor

  • 单线程的线程池,核心线程数量=最大线程数量=1,也就不需要存活时间了
  • 使用无界队列 LinkedBlockingQueue(没指定大小)
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

newScheduledThreadPool

ScheduledExecutorService

可调度的线程池,ScheduledExecutorService 扩展了 ExecutorService 接口,提供了定时任务周期性任务的执行能力

  • 可以在指定延迟后执行任务
  • 可以固定速率或固定延迟重复执行任务

周期性任务如果抛出异常,后续执行会被取消,所以应该在任务内部捕获所有异常

常用方法

// 创建单线程的定时任务执行器
ScheduledExecutorService singleThreadScheduler = Executors.newSingleThreadScheduledExecutor();

// 创建多线程的定时任务执行器(指定线程数)
ScheduledExecutorService multiThreadScheduler = Executors.newScheduledThreadPool(4);

// 指定时间后执行一次任务(Runnable 任务,无返回值)
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);

// 指定时间后执行一次任务(Callable 任务,有返回值)
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);

// 固定频率执行任务(只是指定间隔时间,有可能间隔时间小于任务执行时间)
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);

// 和上面的区别是,这个方法会等任务【执行完成】后等待一定的时间再次执行
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);

使用示例

public class SchedulerExample {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
        
        // 一次性任务
        scheduler.schedule(() -> System.out.println("一次性任务"), 3, TimeUnit.SECONDS);
        
        // 固定速率任务
        scheduler.scheduleAtFixedRate(() -> 
            System.out.println("固定速率任务 - " + LocalTime.now()), 
            1, 2, TimeUnit.SECONDS);
            
        // 固定延迟任务
        scheduler.scheduleWithFixedDelay(() -> {
            System.out.println("固定延迟任务 Start - " + LocalTime.now());
            try {
                Thread.sleep(1000); // 模拟任务执行时间
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("固定延迟任务 End - " + LocalTime.now());
        }, 1, 3, TimeUnit.SECONDS);
        
        // 30秒后关闭调度器
        scheduler.schedule(() -> {
            scheduler.shutdown();
            try {
                if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                    scheduler.shutdownNow();
                }
            } catch (InterruptedException e) {
                scheduler.shutdownNow();
            }
        }, 30, TimeUnit.SECONDS);
    }
}
posted @ 2023-07-12 10:02  CyrusHuang  阅读(16)  评论(0)    收藏  举报