ThreadPoolExecutor 线程池

326 阅读9分钟

为什么要引入线程池?

线程的创建是和运行是耗性能的,而系统资源是有限的。引入线程池,是为了高效的利用系统资源。 线程池,广义上理解就是存放线程的一个池子,一个个具体的线程提交到线程池中即为一个具体的任务。 线程池来管理这些任务的运行。

线程池所做的事情

1、任务提交到线程池

2、任务提交到线程池中,是如何执行的,假设有固定的10个线程来执行。

3、如果任务突然多了,10个线程都在不停的执行任务,新接收的任务,那么把这个任务压着,等空闲时候再执行,则需要一个存放任务的队列,queue,假设是有界的队列,执行存100个。

4、计划的10个线程,和接收的任务不平衡,忙不过来,此时期望能有临时的线程来运行任务,从queue中取出任务来执行,或者新接收的任务交给临时线程来执行。假设计划有20个临时线程。

5、如果固定的10个线程都在忙碌,queue 中已经有了100个任务,20个临时线程也在忙碌,这时候新接收的任务(我们把固定的线程和临时的20个线程统一叫做工作线程),线程池处理不过来了,也就是工作线程满了,队列满了,针对该场景可能会直接丢弃任务,或者告知请求方,牛仔很忙等等处理方式,不妨把此操作定义为线程池的拒绝策略。

6、线程池状态,需要让外界知道,线程池目前处理什么样状态,通过该状态,判断是否可以把任务提交给线程池

7、线程池的状态管理,任务的管理,工作线程的管理

线程池设计原理

先空着

java 线程池体系

java线程池体系结构

1-1-1-1-4线程池体系.png

ThreadPoolExecutor

线程池状态和工作线程数

ctl

runState 线程池运行状态,通过runStateOf()获取

wokerCount 线程池中的工作线程数(核心线程数+非核心线程数),通过workerCountOf()获取

线程池的5个状态

RUNNING , 运行状态,接收新的任务,并执行它

SHUTDOWN , 不再接收新的任务,但阻塞队列中的任务会继续执行

STOP ,不再接收新的任务,阻塞队列中的任务也不再继续执行

DIDYING ,

TERMINATED ,terminated() 执行完

上面提高的固定线程和临时线程,只要它们创建,都作为工作线程,那么肯定需要一个计数器来记录有多少个工作线程。

为此我们需要2个变量,一个来记录线程的状态位,一个用来记录工作线程的数量。

如果用二进制 0 1 来表示线程池的5个状态,至少需要3位(2位只能表示4种状态[00、01、10、11],3位可以表示8种状态)。

用一个变量来表示线程池状态和工作线程数 ctl,它是一个int类型,int 类型 在jvm中占4个字节,每个字节二进制8位表示,即32(1110 0000 0000 0000 000 0000 000 0000 )。

runState,线程池状态如果用二进制,3位即可表示全,所以,ctl的高3位(二进制左面的3位)表示线程池的状态。

wokerCount,工作线程数量 ,32位去掉已经被占用的3为,还有29位,把这29位叫做低29位,用来存储工作线程数量。所以工作线程数量是有上限的。

并提供获取ctl 高3位和低29位的方法 , 获取的结果即为 runState 和 wokerCount 。

线程池状态生命周期

1-1-1-1-1线程池生命周期.svg

    // 高3位表示 runState ,低29位表示 wokerCount 
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3; // 29
    // 1 << 29 再减少1 ,二进制表示 0001 1111 1111 1111 1111 1111 1111 1111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1; 

    // ~ CAPACITY 二进制 1110 0000 0000 0000 0000 0000 0000 0000

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl
    private static int runStateOf(int c)     { 
        return c & ~CAPACITY;  // 获取的是ctl的高3位 
    }
    private static int workerCountOf(int c)  { 
    
        return c & CAPACITY; // 获取的是ctl的低29位
    
    }
    private static int ctlOf(int rs, int wc) { return rs | wc; }
    
    private static boolean isRunning(int c) {
        return c < SHUTDOWN; // 直接判断ctl , SHUTDOWN=0 , running 是负数,贼简单的判断方式
    }

    

构造函数(核心参数)

corePoolSize,核心线程数,指定了线程池计划有多少个核心线程数

maximumPoolSize ,最大线程数,指定线程池最多有多少个临时线程数(非核心线程)

keepAliveTime,非核心线程销毁前的保活时间

unit 时间单位

workQueue , 存放任务的队列,是一个阻塞队列

threadFactory , 创建线程池中工作线程的工厂

handler , 拒绝策略,当线程池满了,执行的拒绝策略

源码如下

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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

拒绝策略

1-1-1-1-线程池拒绝策略.png

线程池拒绝策略,有4种

1、AbortPolicy ,ThreadPoolExecutor 的默认拒绝策略

2、CallerRunsPolicy ,

3、DiscardPolicy ,

4、DiscardOldestPolicy ,

5、拓展拒绝策略,用户可以拓展自己的拒绝策略,通过实现 RejectedExecutionHandler接口,重写rejectedExecution()

execute()

workQueue , 存任务队列,当工作线程数达到了核心线程数,会把任务存放到阻塞队列中

addWorker(Runnable firstTask,boolean core) ,后面会具体届时,此处把它理解成一个创建工作线程即可。

execute(Runnable command) 方法签名:

command ,即要执行的任务

在将来的某个特定时机,执行该任务。可能会创建进一个新的工作线程来执行,也可能复用线程池中已经存在的工作线程来执行。

如果该任务不能被成功提交,可能是因为线程池已经被shutdown 了, 也可能是因为当前线程池已经达到了上限(核心线程数、工作线程数 均达到了上限,任务队列满了)。

如果不能被成功提交,线程池的拒绝策略会生效

execute() 执行流程:

execute() 流程 整体上分为3步骤

1、如果当前工作线程数小于核心线程数,会创建一个核心线程来执行command

2、如果当前的工作线程数,大于核心线程数,把command提交给任务队列 workQueue。此处会执行一个双重检查,检查当前线程池的状态是否是运行中(running),如果不是running,把任务从 workQueue中移除,并执行拒绝策略,如果是running,并且工作线程数小于最大线程数,创建一个非核心线程,addWorker(null,false),这个非核心线程会从workQueue中取任务执行。

3、如果达到了核心线程数上限,任务队列 workQueue 满了,不能继续提交任务。尝试创建一个非核心线程来执行 addWorker(command,false),意思是创建一个非核心线程,并立即执行command。

如果非核心线程不能创建成功,则执行拒绝策略

execute()源码如下:


public void execute(Runnable command) {
    // 省略了常规检查代码 
    
    int c = ctl.get(); // 取ctl 快照
    if (workerCountOf(c) < corePoolSize) { // 当前的工作线程数量小于核心线程数,创建核心线程
        if (addWorker(command, true))  // 因为是并发场景,其他任务抢先了,所以会有创建核心线程失败的场景,失败的场景可能是线程池被外界shutdown,或者线程池核心线程数满了
            return;  // 如果核心线程成功创建,则直接返回 ,否继续流程
        c = ctl.get();  // 重新取ctl 快照,因为上面已经创建核心线程数失败了,c 不是最新的了
    }
    
    // 如果当前线程池状态已经不是运行中了,会直接走拒绝策略(外界关闭了线程池)
    if (isRunning(c) && workQueue.offer(command)) { //当前线程池是running,则把任务添加到阻塞队列中,
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command); // 任务添加到阻塞队列中,要做二次检查,当前线程池如果被关闭要从阻塞队列中移除任务,并执行拒接策略,
        else if (workerCountOf(recheck) == 0) // 如果当前的工作线程数小于最大线程数,新建非核心线程
            addWorker(null, false); // 创建非核心线程 firstTask 是null core 是 fase 
    }
    else if (!addWorker(command, false))
    reject(command);  // 达到了核心线程数,阻塞队列满了,也不能创建非核心线程了,执行拒接 和 上面的拒接2个是不一样场景触发的
}

addWorker()

addWorker()方法签名:

创建一个工作线程,可以是核心线程,也可以是非核心线程(core maxi queue 控制)。

通过给定的参数 (firstTask 、 core)和线程池状态,在线程池中创建工作线程。线程池状态控制能不能创建线程,给定的参数控制创建什么样的线程,核心线程或者非核心线程。

工作线程成功创建并成功启动之后,线程池中工作线程数量也会随之调整。

如果给了第一个任务,新创建的工作线程会执行第一个任务。

firstTask 是否是第一个任务(是不是Worker执行的第一个任务),如果是第一个任务,则不能是空,这个新建的工作线程会执行这个任务。创建核心线程 firstTask 肯定不是空的,非核心是空或者不是空。

core , 核心线程 和 非核心线程的标识, true 为核心线程,false 为非核心线程 addWorker(Runnable firstTask,boolean core)

预备知识:

doug lea 老早前是写C的,lea老爷写Java的习惯会带有写C的思想在里面,变量名称,判断语句处理等。

// 定义一个预置点bet
bet:
for(;;){
    inner: // 定义一个预置点 inner
    for(;;){
        break inner; // 跳出内层循环 类似 C的goto 语法,Java 没有 goto 
    }
    break bet; // 会跳出外层循环 
}

System.out.println("the end"); 

Worker 类的定义

继承AQS , 并实现 Runnable 接口 。

final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable{
       
       // 省略其他细节
       
       final Thread thread; // Worker 是一个任务线程,thread 来执行 Worker 
       
       Runnable firstTask; //初始化的任务,addWorker 的 firstTask 参数,创建非核心线程,且不是第一个任务时候,firstTask 是 null 
       
       
       volatile long completedTask; // 
       
       
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker  AQS 的 state 
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); // 线程工厂创建的线程 入参是 this ,即当前的 Worker 
    }


       
       public void run(){
           runWorker(this);   // 调度的是TreadPoolExecutor#runWorker() 
       }
       
       // 重写 AQS 的  钩子函数 
    }

addWorker() 执行流程:

死循环for,套死循环for,看到这不要慌,这是doug lea 的写法, 拆开看,整体上分为2块内容 :

1、2个for循环,是检查,类似卫语句写法,先把不成立的摘出来 return ,如果条件成立CAS 操作 工作线程数量+1(如果后续操作失败,再回滚) 。

2、创建 Worker(工作线程) ,并把Worker 添加到 workers 中,成功添加到 workers 中的 Worker 会被启动

3、(finally 事情)如果Worker 被添加到 workes 中,则启动线程,如果没有则回滚,把此处和1、2操作分开,但是它们是有关系的,而且非常重要(工作线程的启动)。

如下是 workers 的定义, HashSet

final HashSet<Worker> workers = new HashSet<Worker>() // addWorker() 中 新建的线程会被添加到 workers 中,并如果添加到workers 中则会启动该线程,否则会从 workers 中回滚 Worker 

addWorker() 源码

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:   // 类似 goto 语句 用于跳出循环
    
    // for(;;) 方式,退出循环肯定是有多种场景条件满足退出
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);  // runState 线程池运行状态

        /*
        1、RUNNING 状态会接收新任务,
        2、SHUTDOWN 状态,不会接收新任务,但是会继续执行queue中的任务
        
        3、如果 rs > SHUTDOWN , 直接结束 ; 否则对里面组合(&&关系)操作取反 组合中有一个是false 则 取反就是true 
        
        如果 rs = SHUTDOWN ,还要判断firstTask 是不是第一个任务,
        */
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN // 如果外部调用了(或者已经调用了) shutdown() 或者shutdownNow(),线程池已经SHUTDOWN 了,
            && // SHUWDOWN 状态是不在接收新的任务,但是queue中的任务会继续执行
            ! (
               // 线程成状态是SHUTDOWN 并且不是第一个任务,并且queue 不为空(阻塞队列中有任务没有执行) ,需要消费调queue里面的任务,
               rs == SHUTDOWN 
               &&
               firstTask == null
               &&
               ! workQueue.isEmpty()
               )
            )
            return false;

        for (;;) {
            int wc = workerCountOf(c); // 取工作线程数量
            
            
            if (
                 // || 关系,只要一个不成立则结束,不创建 Worker
                  wc >= CAPACITY  // 如果工作线程数达到了池的上线(int类型的低29全是1),则不创建线程 
                 ||
                  // core = true , 和核心线程数比较,false 和 最大线程数(核心线程数+非核心线程数)比较,如果达到了阈值,也不Worker ,直接结束
                  wc >= (core ? corePoolSize : maximumPoolSize)
                
                )
                return false;
            if (compareAndIncrementWorkerCount(c)) // doug lea 喜欢的做法,1、先改状态 2、再做操作 3、如果失败则回滚
                break retry;  // CAS 操作成功 则跳出循环,并往下执行 创建Worker
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)  // CAS 操作失败,但快照 rs 和 线程池运行状态不一致,即快照失败了,再循环尝试一次  continue 操作
                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 {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                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();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

runWorker()

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts,对应Worker 构造函数setSate=-1(-1 不能被中断),unlock 之后 容许w被中断 ,非常重要!!!
    boolean completedAbruptly = true;
    try {
         // 1、如果task 不为空则表示是 w的第一个任务,
         // 2、如果task 为 空,则从 workerQueue 中取任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if (
                  // 1、runState >= STOP 并且 wt 没有被中断,则会中断wt
                  // 2、 主线程中断了,并且再次检查runState是>=STOP ,并且 wt没有被中断,则会中断wt
                  // 3、 如果 1 和 2 都不成立,不需要中断wt,而是去执行wt
                  (
                      runStateAtLeast(ctl.get(), STOP) // runState >= STOP
                      ||
                      (
                         Thread.interrupted()  // 
                         &&
                         runStateAtLeast(ctl.get(), STOP)
                      )
                 ) 
                  
                  &&
                !wt.isInterrupted()
                )
                wt.interrupt();
            try {
                beforeExecute(wt, task);  // 预留方法,可以拓展
                Throwable thrown = null;
                try {
                    task.run();  // 直接调用task的run方法,执行run的方法体,wt 已经是具备线程的功能,并且是启动了,执行执行task的run方法体积
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown); // 预留方法,可以拓展
                }
            } finally {
                task = null;  // task 清楚等待GC回收
                w.completedTasks++;
                w.unlock();  // 释放w的锁
            }
        }
        completedAbruptly = false;  
    } finally {
       // completedAbruptly = true 有3种场景 :
       // 1、 getTask() 抛了异常 
       // 2、 beforeExecute() 抛了异常,针对自定义线程池会有
       // 3、 afterExecute() 抛了异常,针对自定义线程池会有
        processWorkerExit(w, completedAbruptly);
    }
}

Thread中断相关方法

    // 获取线程中断的状态为,并中断线程
    public static boolean interrupted() {
        return currentThread().isInterrupted(true); // 给 true 会中断线程
    }
    
    
    // 获取中断状态位
    public boolean isInterrupted() {
        return isInterrupted(false); // 入参给false 不会中断线程
    }
    
    // native 方法,参数 ClearInterrupted , 为true 会中断线程,false 不会中断线程
    private native boolean isInterrupted(boolean ClearInterrupted);
    

getTask()

待补充

processWorkerExit()

待补充

tryTerminated()

待补充

拓展线程池

1、如何监控线程池任务执行的异常?

1)、拓展自定义的线程池,task.run 地方进行try catch ,并处理,ThreadPoolExecutor 是甩到 thrown 中了。

2)、拓展自定义的线程池 ,在 (afterExecute(task, thrown); // 预留方法,可以拓展),判断 thrown ,并处理。thrown 捕捉的是 task.run 异常 。

3)、 自定义创建线程池工作线程的工场,并重写newThread(), 并利用Thread#setUncaughtExceptionHandler(), 并自定义 实现UncaughtExceptionHandler 接口

如下是一个示例

public static class JfThreadFactory implements ThreadFactory{


    @Override
    public Thread newThread(Runnable r){
        Thread t = new Thread(r);

        t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){

            @Override
            public void uncaughtException(Thread t, Throwable e){

                System.out.println("我擦,异常:"+e.getMessage());
            }
        });

        return t;
    }

}

public static class JfThreadPool extends ThreadPoolExecutor {


    public JfThreadPool(int corePoolSize,
                        int maximumPoolSize,
                        long keepAliveTime,
                        TimeUnit unit,
                        BlockingQueue<Runnable> workQueue,
                        ThreadFactory threadFactory,
                        RejectedExecutionHandler handler){

        super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);

        // 重新 afterExecute 处理 t 异常
        if(null != t){
            System.out.println("thrown ="+t.getMessage());
        }

        // 如果展开下面的异常, uncaughtException 将会捕捉数组越界异常 覆盖 除数为0异常
        //int[] arr = new int[3];
         //arr[3]=10; // 数组越界异常 Jf
    }
}


public static void main(String[] args) {


    JfThreadPool pool =  new JfThreadPool(1,1,1,TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(10),new JfThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()
            );
    pool.execute( () -> {
        System.out.println("1任务执行!!!");
        int c = 1/0;  // 线程池如何捕获此异常
    });

    pool.execute( () -> {
        int c = 1/0;
        System.out.println("任务执行!!!");
    });

    pool.execute( () -> {
        int c = 1/0;
        System.out.println("任务执行!!!");
    });






}

2、线程任务监控器,为什么会出现丢任务情况,而且啥提示都没有

    try{
        int c = 1/0;
    } // 没有catch {},会吞掉异常
    finally{
        // 代码
    }

finally {
    // 没有任何处理,吞了异常没有catch
    task = null;
    w.completedTasks++;
    w.unlock();
}

3、 processWorkerExit(),中捕捉的异常是什么异常,从哪里抛出来的?

是getTask() 抛出的异常,看下图

1-1-1-1-runWorker抛出异常.png

除了getTask() 抛出异常, completedAbruptly = true ,还有 2处:

其一: beforeExecute()

其二: afterExecute()

       // completedAbruptly = true 有3种场景 :
       // 1、 getTask() 抛了异常 
       // 2、 beforeExecute() 抛了异常,针对自定义线程池会有
       // 3、 afterExecute() 抛了异常,针对自定义线程池会有
        processWorkerExit(w, completedAbruptly);
    }

但是(很重要!!!),getTask() 的异常在底层被吞掉了,所以只有用户拓展线程池,并重写 beforeExecute() 或者 afterExecutor() ,而且还写了bug 。 才会导致 completedAbruptly = true。

如下是getTask() 部分代码 和

   getTask() 代码片段
    try {
        Runnable r = timed ?
            workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            workQueue.take();
        if (r != null)
            return r;
        timedOut = true;
    } catch (InterruptedException retry) {
        timedOut = false;
    }
    
    
    
    /**
    *
    * @param completedAbruptly if the worker died due to user exception
      用户导致的worker线程异常,worker计数不会减少
     */
    private void processWorkerExit(Worker w, boolean completedAbruptly){
    
    
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();
        
        // 省略其他代码
    }
    
    
    
    processWorkerExit() 代码片段 如下
    
    
    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
    }



待补充