【多线程编程】--ThreadPoolExecutor解析

一、ThreadPoolExecutor前述

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
说明
ThreadPoolExecutor是我们最熟悉的,下面将具体介绍ThreadPoolExecutor源码、原理、使用中的细节、常见问题等等。

二、ThreadPoolExecutor源码

2.1、ThreadPoolExecutor参数

实际业务中,通过继承重新ThreadPoolExecutor类,构建自己需要的线程池。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) ;

corePoolSize:核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。
即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。刚创建时,核心线程不会启动。当有任务提交时才启动,或调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程。
maximumPoolSize:线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。
keepAliveTime:非核心线程的闲置超时时间,超过这个时间就会被回收【就是外包员工线程被回收】。[如果设置allowCoreThreadTimeOut(boolean value),会作用于核心线程]
TimeUnit:时间单位。
workQueue:线程池中任务队列。常用的有:SynchronousQueue【一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue】,LinkedBlockingDeque【一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列】,ArrayBlockingQueue【是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序】、PriorityBlockingQueue【一个具有优先级的无限阻塞队列】。
A、如果当前线程池的线程数还没有达到基本大小(poolSize < corePoolSize),无论是否有空闲的线程新增一个线程处理新提交的任务;
B、如果当前线程池的线程数大于或等于基本大小(poolSize >= corePoolSize) 且任务队列未满时,就将新提交的任务提交到阻塞队列排队,等候处理workQueue.offer(command);
C、如果当前线程池的线程数大于或等于基本大小(poolSize >= corePoolSize) 且任务队列满时;
(A-1)当前poolSize<maximumPoolSize,那么就新增线程来处理任务;
(A-2)当前poolSize=maximumPoolSize,那么意味着线程池的处理能力已经达到了极限,此时需要拒绝新增加的任务。至于如何拒绝处理新增的任务,取决于线程池的饱和策略RejectedExecutionHandler。
ThreadFactory:线程创建的工厂。可以进行一些属性设置,比如线程名,优先级等等,有默认实现。
RejectedExecutionHandler:任务拒绝策略,当运行线程数已达到maximumPoolSize,队列也已经装满时会调用该参数拒绝任务,默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。
AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务。
DiscardOldestPolicy:丢弃队列里最老的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。

2.2、FutureTask源码解读

在这里插入图片描述
FutureTask类的run()方法:
将runner属性设置成当前正在执行run方法的线程;
调用callable成员变量的call方法来执行任务;
设置执行结果outcome, 如果执行成功, 则outcome保存的就是执行结果;如果执行过程中发生了异常, 则outcome中保存的就是异常,设置结果之前,先将state状态设为中间态;
对outcome的赋值完成后,设置state状态为终止态(NORMAL或者EXCEPTIONAL);
唤醒Treiber栈中所有等待的线程;
善后清理(waiters, callable,runner设为null);
检查是否有遗漏的中断,如果有,等待中断状态完成。

2.3、ThreadPoolExecutor源码解读

ThreadPoolExecutor构造函数主要是给下面几个变量赋值

   private volatile int corePoolSize; //核心线程数
    private volatile int maximumPoolSize; //最大线程数
    private final BlockingQueue<Runnable> workQueue; //存放线程的队列
    private volatile long keepAliveTime; //存活时间
    private volatile ThreadFactory threadFactory;//创建的工厂 (默认 new DefaultThreadFactory())
    private volatile RejectedExecutionHandler handler; //多余的的线程处理器(拒绝策略,默认 new AbortPolicy())

2.3.1、线程池状态

在这里插入图片描述
线程池一旦被创建,就处于running状态,此时线程中任务个数为0;
如果调用线程池的shutdown(),那么会进入shutdown状态,此时不接受新任务,只会把现有提交的任务完成;
如果调用线程池的shutdownNow(),那么会进入shop状态【可以从runningshop 或者shutdownshop】,此时不接受新任务,且现有提交的任务也会中断;
线程池任务都终止,ctl记录的任务数量也为0,此时变成tidying状态;
线程池在tidying状态时,执行完terminated()方法后,变成terminated状态,最终终止。

2.3.2、execute()方法

在这里插入图片描述
向线程池提交任务时,都是用execute()方法执行。线程池维护一批线程处理用户提交的任务,维护的一批线程就是封装成Worker。

//获取当前线程池的状态+线程个数变量
        int c = ctl.get();
        /**
        1.当前线程池线程个数 < corePoolSize,继续使用“核心线程”添加任务。
           调用addWorker方法创建新线程运行且传进来的Runnable当做第一个任务执行。
         */
        if (workerCountOf(c) < corePoolSize) {
   
    
    
            if (addWorker(command, true))
                return;  //添加成功就返回,否则重新获取线程池个数和状态
            c = ctl.get();
        }
        /**
         2. 当前线程 >= corePoolSize,或之前添加线程任务失败,且线程池是运行RUNNING状态,继续往队列塞任务
         */
        if (isRunning(c) && workQueue.offer(command)) {
   
    
    
            int recheck = ctl.get();
            // 再次获取线程池的状态, 如果线程池不是running,则从队列中删除任务,并执行拒绝策略A && B,如果 A是假,那么B就不执行;如果A是真,那么B必须执行】
            if (! isRunning(recheck) && remove(command))
                reject

猜你喜欢

转载自blog.csdn.net/xunmengyou1990/article/details/128888265