线程池 ThreadPoolExecutor

工作流程

  1. 提交任务时,如果线程池当前线程数量小于核心线程数时,直接创建线程来处理任务(线程池初始不会创建线程,当有任务提交才创建)
  2. 当线程池线程数量达到核心线程数,任务进入队列
  3. 当队列满了不能继续放时,再次创建线程来处理任务
    1. 这时的线程叫救急线程
    2. 当救急线程处理完任务空闲时,达到存活时间后会销毁救急线程
    3. 救急线程也会到队列以此获取任务(比如当提交第100个任务时创建的救急线程,但是这个救急线程不会执行第100个任务,而是会到队列里获取任务)
  4. 核心线程数达到阈值、队列满了、最大线程数也达到阈值,执行拒绝策略

一个变量保存线程个数和状态

// 线程数量
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池最大线程容量
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// 下面5个属性表示线程池不同状态
// 运行中
private static final int RUNNING    = -1 << COUNT_BITS; 
// 停止中(不是停止,是停止中。不会接收新任务,但是已经收到的任务会继续处理)调用 shotdown() 方法进入该状态
private static final int SHUTDOWN   =  0 << COUNT_BITS; 
// 停止中(不接受新任务,丢弃已经接收到的任务,正在执行的任务会中断)调用 shotdownNow() 方法进入该状态
private static final int STOP       =  1 << COUNT_BITS;
// 过度状态,当活动线程为 0 即将进入终结状态时的状态
private static final int TIDYING    =  2 << COUNT_BITS;
// 总结状态,真正的停止
private static final int TERMINATED =  3 << COUNT_BITS;

数量和状态都是一个变量 COUNT_BITS 保存的,高 3 位存储线程状态,低 29 位表示线程数量

为什么要一个变量保存两个信息?定义两个变量不是更用以理解吗?如果两个变量就要保证两次原子操作,放到一个变量里只需要一次原子操作

构造方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

各个参数解释

参数 含义 说明
corePoolSize 核心线程数
maximumPoolSize 最大线程数 核心线程数+救急线程数=最大线程数
keepAliveTime 救急线程存活时间(长度) 当核心线程和队列都满负荷,会创建救急线程,当救急线程不需要时会被回收
如果队列是无界队列,就不会创建救急线程,但是要谨防内存不足
unit 救急线程存活时间(单位)
workQueue 阻塞队列 为什么要阻塞队列?当任务提交和执行速度不一致时:
对于生产者,队列满了不能再接收任务时阻塞任务提交
对于消费者,队列没有任务时,阻塞获取任务,直到有任务进入队列
threadFactory 线程工厂 起个好名字方便排查问题,不然都是默认的名字不好区分
handler 拒绝策略 当核心线程、队列、救急线程都满负荷时执行

拒绝策略

JDK 提供的拒绝策略:

  • AbortPolicy:会抛出 RejectedExcutionException 异常(默认策略)
  • CallerRunsPolicy:让调用者执行任务(如果不在自己创建线程提交任务,那就是主线程)
  • DiscardPolicy:放弃任务,也不会有任何反馈,直接抛弃任务(抛弃新来的任务)
  • DiscardOldestPolicy:也是放弃任务,但是和 DiscardPolicy 不一样的是 DiscardOldestPolicy 会抛弃最早的任务(抛弃原有的任务)

三方框架扩展的拒绝策略:

  • Dubbo 的实现:扩展 AbortPolicy,在抛出 RejectedExcutionException 前会记录日志,并 dump 线程栈信息
  • Netty 的显现:创建一个新的线程来执行任务
  • ActiveMQ:带超时等待(60s),时间到了再次尝试放入队列

工厂方法创建线程池

ExecutorService 类提供了一些便捷的方法创建线程池,底层都是利用 ThreadPoolExecutor 构造方法创建的

  • 创建固定线程数量的线程池

    核心线程数 = 最大线程数,意味着没有救急线程;队列是无界的

    // java.util.concurrent.Executors#newFixedThreadPool(int, java.util.concurrent.ThreadFactory)
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    
  • 创建带缓冲功能的线程池(取名有点词不达意,本质是不带缓存功能的线程池)

    核心线程数是0,最大线程数是 Integer.MAX_VALUE,全部都是救急线程(存活时间 60s)

    SynchronousQueue 特点是不存放任务,有消费者在等待时才能把任务放入队列,刚放进去消费者就取走了(队列不会缓存任务),原理是调用 offer 或 put 方法时,如果没有等待线程会阻塞

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
  • 创建单个线程的线程池

    核心线程数 = 最大线程数 = 1,也不会创建救急线程,队列是无界的,任务不会并发执行,一个一个串行执行

    返回的是 FinalizableDelegatedExecutorService 不是 ThreadPoolExecutor(装饰器模式),和 ExecutorService.newFixedThreadPool(1) 还是有区别的,ExecutorService.newFixedThreadPool(1) 强转后还是可以修改线程数量

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

提交任务

// 执行 Runnable 任务
void execute(Runnable command);

// 提交 Callable 任务,有返回值
<T> Future<T> submit(Callable<T> task);

// 提交 Runnable 任务,有返回值,内部用 FetureTask 包装了 Runnable
Future<?> submit(Runnable task);

// 批量提交任务
<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;
	
// 批量提交任务,哪个任务先完成,返回哪个任务的结果,其他任务取消
<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;
posted @   CyrusHuang  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示