ThreadPoolExecutor线程池详解

ThreadPoolExecutor线程池详解

1. 用法

1.1 构造方法

ini 复制代码
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;
    }
​

1.2 线程池的使用

  • worker
typescript 复制代码
/**
 * @author itender
 * @date 2023/8/7 14:41
 * @desc
 */
public class Worker implements Runnable {
​
    private String command;
​
    public Worker(String s) {
        this.command = s;
    }
​
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + command + " startTie = " + DateUtil.now());
        processCommand();
        System.out.println(Thread.currentThread().getName() + command +  " endTime = " + DateUtil.now());
    }
​
    private void processCommand() {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + command +" 处理任务逻辑。。。。。。。。");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
​
    @Override
    public String toString() {
        return this.command;
    }
}
  • 线程池
java 复制代码
/**
 * @author itender
 * @date 2023/8/7 14:37
 * @desc
 */
public class ThreadPoolExecutorDemo {
    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 100;
    private static final Long KEEP_ALIVE_TIME = 1L;
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // 核心线程数 5
                CORE_POOL_SIZE,
                // 最大线程数 10
                MAX_POOL_SIZE,
                // 超过核心线程数,线程最大存活时间
                KEEP_ALIVE_TIME,
                // 时间单位
                TimeUnit.MINUTES,
                // 工作队列最大值
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                // 线程工厂,创建线程的时候使用
                r -> {
                    Thread thread = new Thread(r);
                    thread.setName("pool-");
                    return thread;
                },
        new ThreadPoolExecutor.CallerRunsPolicy()
        );
        for (int i = 0; i < 10; i++) {
            // 创建任务
            Worker myRunnable = new Worker("" + i);
            // 执行任务
            threadPoolExecutor.execute(myRunnable);
        }
        // 种植线程池,不接受新任务,但是有工作线程处理队列中的任务
        threadPoolExecutor.shutdown();
​
        while (!threadPoolExecutor.isTerminated()) {
​
        }
        System.out.println("Finished All Threads!");
    }
}

2. 核心参数

2.1 核心参数详解

  • corePoolSize:核心线程数,任务队列没有达到队列最大容量时,最大可以同时运行的线程数。
  • maximumPoolSize:最大线程数。当任务队列中存储的任务达到队列的容量时,当前可以同时运行的线程数量变为最大线程数。
  • keepAliveTime :线程池中的线程数量超过corePoolSize时,如果没有新任务提交,核心线程外的线程不会立即销毁,而是等待,直到等待的时间超过了keepAliveTime才会被销毁回收。
  • unitkeepAliveTime参数的时间单位。
  • workQueue:工作队列。当有新的任务提交的时候,会先判断当前运行的线程数是否达到核心线程数,如果达到核心线程数,则会把新提交的任务放到工作队列中。
  • threadFactory:线程工厂,创建新的线程时会使用。
  • handler:拒绝策略。

2.2 拒绝策略

如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolTaskExecutor 定义一些策略:

  • AbortPolicy:默认拒绝策略。抛出RejectExecutionException来拒绝新任务的处理。
  • CallerRunsPolicy:调用当前提交任务的线程来执行任务。
  • DiscardPolicy:不处理新任务,直接丢弃。
  • DiscardOldestPolicy:丢弃最早的未处理的任务。

3. 执行流程

4. 线程池状态

4.1 线程池核心属性ctl

java 复制代码
    // ctl本质是 Integer 型变量,进行了原子性的封装
    // ctl表示两种状态:
    // 高3位:线程池当前的状态
    // 低29位:线程池当前工作线程的数量
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // COUNT_BITS 的值为 29(整型Integer.SIZE = 32 位);
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // CAPACITY = (1 << 29) - 1;1左移29位,减去1;即1*2^29-1;
    // 0001 1111 1111 1111 1111 1111 1111 1111
    // 低29位用来表示线程池的最大线程容量
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    
    // 高3位用来表示线程池5种状态
    // 111
    private static final int RUNNING    = -1 << COUNT_BITS;
    // 000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    // 001
    private static final int STOP       =  1 << COUNT_BITS;
    // 010
    private static final int TIDYING    =  2 << COUNT_BITS;
    // 011
    private static final int TERMINATED =  3 << COUNT_BITS;
    // 根据ctl的值,计算当前线程池的状态
    // 计算方式:c 与 非capacity
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    // 根据ctl的值,计算线程池当前运行的线程的容量
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    // 通过运行状态和工作线程数计算ctl的值,或运算
    private static int ctlOf(int rs, int wc) { return rs | wc; }
​
    private static boolean runStateLessThan(int c, int s) {
        return c < s;
    
    private static boolean runStateAtLeast(int c, int s) {
        return c >= s;
    }
    
    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }

4.2 状态切换

  • RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务。
  • SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown() 方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用 shutdown() 方法进入该状态)。
  • STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态。
  • TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入 TERMINATED 状态。
  • TERMINATED:在 terminated() 方法执行完后进入该状态,默认 terminated() 方法中什么也没有做。

5. execute方法

  • 源码
scss 复制代码
public void execute(Runnable command) {
        // 判断任务是否为空,如果任务为空,抛出空指针异常
        if (command == null)
            throw new NullPointerException();
        // 获取ctl属性
        int c = ctl.get();
        // 判断当前工作线程数量是否小于核心线程的数量
        if (workerCountOf(c) < corePoolSize) {
            // 工作线程数小于核心线程数,创建一个核心线程执行command任务
            if (addWorker(command, true))
                // 创建核心线程成功,直接返回
                return;
            // 并发情况下添加核心线程失败,需要重新获取ctl属性
            c = ctl.get();
        }
        // 当前工作线程数量大于或等于核心线程数量
        // 判断线程池的状态是否为running,如果是添加任务到工作队列中
        if (isRunning(c) && workQueue.offer(command)) {
            // 任务添加到队列成功,再次获取ctl属性
            int recheck = ctl.get();
            // 判断线程池的状态是否为running,如果不是队列中移除刚刚添加的任务
            if (!isRunning(recheck) && remove(command))
                // 执行拒绝策略
                reject(command);
            // 判断工作线程数量是否为0
            else if (workerCountOf(recheck) == 0)
                // 工作线程数量为0
                // 工作队列中有任务在排队,添加一个空任务,创建非核心线程执行队列中等待的任务
                addWorker(null, false);
        }
        // 添加任务到工作队列失败,创建非核心线程执行任务
        else if (!addWorker(command, false))
            // 创建非核心线程失败,执行拒绝策略
            reject(command);
    }

第一点核心:通过execute方法源码可以看出线程池具体的执行流程,以及一些避免并发情况的判断。

第二点核心:线程池为什么会添加空任务非核心线程到线程池。

6. addWorker方法

  • 源码
ini 复制代码
private boolean addWorker(Runnable firstTask, boolean core) {
    // for循环标识
    // 对线程池当前状态和当前工作线程数量的判断
    retry:
    for (;;) {
        // 获取线程池的状态
        int c = ctl.get();
        int rs = runStateOf(c);
​
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
​
        for (;;) {
            // 获取线程池工作线程的数量
            int wc = workerCountOf(c);
            // core参数为false说明工作队列已经满了,线程池大小变为maximumPoolSize最大线程数
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 原子操作将workCount的数量加1
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // 如果线程池的状态改变了在执行上面的操作
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    // 工作线程是否启动成功
    boolean workerStarted = false;
    // 工作线程是否创建成功
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            // 加锁
            mainLock.lock();
            try {
                // 获取线程池状态
                int rs = runStateOf(ctl.get());
                //rs < SHUTDOWN 如果线程池状态依然为RUNNING,并且线程的状态是存活的话,就会将工作线程添加到工作线程集合中
                //(rs=SHUTDOWN && firstTask == null)如果线程池状态小于STOP,也就是RUNNING或者SHUTDOWN状态下,同时传入的任务实例firstTask为null,则需要添加到工作线程集合和启动新的Worker
                // firstTask == null证明只新建线程而不执行任务
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    // 更新当前工作线程的最大容量
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    // 工作线程是否添加成功
                    workerAdded = true;
                }
            } finally {
                // 释放锁
                mainLock.unlock();
            }
            // 如果成功添加工作线程,则调用Worker内部的线程实例t的Thread#start()方法启动真实的线程实例
            if (workerAdded) {
                // 启动线程,标识线程启动成功
                t.start();
                workerStarted = true;
            }
        }
    } finally {
         // 线程启动失败,需要从工作线程中移除对应的Worker
        if (!workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

参考文章

www.throwx.cn/2020/08/23/...

javaguide.cn/java/concur...

相关推荐
凡人的AI工具箱5 小时前
AI教你学Python 第11天 : 局部变量与全局变量
开发语言·人工智能·后端·python
是店小二呀5 小时前
【C++】C++ STL探索:Priority Queue与仿函数的深入解析
开发语言·c++·后端
canonical_entropy5 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
我叫啥都行6 小时前
计算机基础知识复习9.7
运维·服务器·网络·笔记·后端
无名指的等待7127 小时前
SpringBoot中使用ElasticSearch
java·spring boot·后端
.生产的驴7 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
AskHarries8 小时前
Spring Boot利用dag加速Spring beans初始化
java·spring boot·后端
苹果酱05678 小时前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件
掐指一算乀缺钱8 小时前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
计算机学姐11 小时前
基于python+django+vue的影视推荐系统
开发语言·vue.js·后端·python·mysql·django·intellij-idea