Java线程池总结-层层剖析线程池源码

363 阅读6分钟

1. 线程池的核心组件以及核心类

      Java中的线程池是通过Executor框架实现的,在该框架中用到的ExecutorExecutors,ExecutorService,ThreadPoolExecutor,callAble,FuturRe,FutureTask这几个核心类,具体的关系如下图所示:

image.png

  • Executor,线程池的顶级接口,仅提供一个无返回值的提交任务方法void execute(Runanble runable)方法。
  • ExecutorService:派生Executor,扩展了很多功能,如:关闭线程池,提交任务并返回结果数据,唤醒线程池的任务等
  • AbstractExecutorService:派生自ExecutorService接口,实现了几个非常实现的方法,供子类进行调用

2. 线程池 -ThreadPoolExecutor

2.1 线程池状态

image.png 注:线程池的状态只能从左到右扭转

  • RUNING():线程池运行状态,能接受新的任务,也能处理队列里的任务
  • SHUTDOWN:关闭状态,不能再接受新的任务,但是可以处理阻塞队列已经保存的任务,当线程池处于Running状态的时候,可用过调用shutDown方法,使线程池处于该状态
  • STOP:不能接受任务,也不能处理阻塞队列已经保存的任务,调用shutDownNow方法,会使线程池处于该状态;shutDownNow方法,会手动清空任务队列的task, 并返回任务队列的task集合。
  • TIDYING:如果所有的任务都已经终止,有效线程数为0(阻塞队列为空,线程池中的工作线程数量
    为0),线程池就会进入该状态
  • terminted:处于Tidying状态的线程池调用terminated()方法,会使用线程池进入该状态

合理配置线程池建议:

  • CPU密集型:CPU+1
  • IO密集型:CPU*2+1

2.2 线程池7大参数及工作流程

image.png

  • corePoolSize : 核心线程数
  • maximumPoolSize:最大线程数
  • workQueue:任务队列
  • keepAliveTime:线程没有任务执行时最多保持多久时间终止,当线程池中的线程数量大于corePoolSize时,如果此时没有新的任务提交,核心线程外的线程不会立即销毁,需要等待,直到等待的时间超过了keepAliveTime就会终止
  • unit:keepAliveTime的时间单位
  • threadFactory:线程工厂,用来创建线程,默认会提供一个默认的工厂来创建线程,当使用默认的工厂来创建线程时,会使新创建的线程具有相同的优先级,并且是非守护的线程,同时也设置了线程的名称
  • rejectHandler:拒绝处理任务时的策略,如果workQueue阻塞队列满了,并且没有空闲的线程池,此时,继续提交任务,需要采取一种策略来处理这个任务;jdk提供了四种默认拒绝策略:
    • 直接抛出异常,实现类为AbortPolicy
    • 用调用者所在的线程来执行任务,实现类为CallerRunsPolicy
    • 丢弃队列最靠前的任务,然后再调用提交任务的方法提交任务,实现类为DiscardOldestPolicy
    • 直接丢弃当前任务,实现类为DiscardPolicy

2.3 Executors-创建线程池的常用类

2.3.1 newCachedThreadPool

功能:创建一个可缓存的线程池,如果线程池的大小超过了需要,可以灵活回收空闲线程,如果没有可回收线程,则需要新建线程

原理:线程池参数,核心线程为0,最大线程数据取Integer.MAX_VALUE,keepAliveTime 为60s,阻塞队列为SynchronousQueue

2.3.2 newFixedThreadPool

功能:创建一个定长线程池,可控制线程的最大并发数,超出的线程会在队列中等待

原理:核心线程数和最大线程数为同一个相同参数,keepAliveTime为0,阻塞对垒为LinkedBlockingQueue

2.2.3 newScheduledThreadPool

功能:创建一个支持定时周期性执行任务的线程池

原理:创建ScheduledThreadPoolExecutor,核心线程参数为入参,最大线程数为Integer.MAX_VALUE,keepAliveTime为0,阻塞队列为DelayedWorkQueue

2.2.4 newSingleThreadExecutor

功能:创建一个单线程化的线程池

原理:核心线程数和最大线程数都为1,keepAliveTime为0,阻塞队列为LinkedBlockingQueue

2.3.5 为什么不能用JDK提供的默认线程池

  • 任务队列无界
  • 线程池工厂无法自定义

3.ThreadPoolExecutor源码解析

3.1 核心字段属性

// 1.可以看做一个int类型的数字,高3位表示线程池状态,低29位表示worke线程的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 2.Integer.SIZE为32,所以COUNT_BITS为29  
private static final int COUNT_BITS = Integer.SIZE - 3;  
// 3. `CAPACITY`,线程池允许的最大线程数。1左移29位,然后减1,即为 2^29 - 1  
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
// 4. 线程池有5种状态,按大小排序如下:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED  
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;
// 5. `runStateOf()`,获取线程池状态,按位与操作,低29位全部变成0  
private static int runStateOf(int c)     { return c & ~CAPACITY; }  
// 6. `workerCountOf()`,获取线程池worker数量,按位与操作,高3位将全部变成0  
private static int workerCountOf(int c)  { return c & CAPACITY; }  
// 7. `ctlOf()`,根据线程池状态和线程池worker数量,生成ctl值  
private static int ctlOf(int rs, int wc) { return rs | wc; }

3.2 构造方法

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;  
    // 根据传入参数`unit`和`keepAliveTime`,将存活时间转换为纳秒并存到变量`keepAliveTime ` 
    this.keepAliveTime = unit.toNanos(keepAliveTime);  
    this.threadFactory = threadFactory;  
    this.handler = handler;  
}

3.3 execute()-提交任务方法

public void execute(Runnable command) {  
    if (command == null)  
        throw new NullPointerException();   
    int c = ctl.get();  
    // worker数量比核心线程数小,直接创建worker执行任务  
    if (workerCountOf(c) < corePoolSize) {  
        if (addWorker(command, true))  
            return;  
        c = ctl.get();  
    }  
    // worker数量超过核心线程数,任务直接进入队列  
    if (isRunning(c) && workQueue.offer(command)) {  
        int recheck = ctl.get();  
        // 线程池状态不是RUNNING状态,说明执行过shutdown命令,需要对新加入的任务执行reject()操作。 
        // 因为线程池状态可能发生变化,所以进入队列前,需要再check一下
        if (! isRunning(recheck) && remove(command))  
            reject(command);    
        // 在线程池构造方法中,核心线程数允许为0,所以需要判断一下是否是0值
        else if (workerCountOf(recheck) == 0)  
            addWorker(nullfalse);  
    }  
    // 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务 
    else if (!addWorker(command, false))  
        reject(command);  
}
  • addWorker内部会判断线程池状态,其第二个参数表示是否会创建核心线程,如果返回false,则会执行reject操作。

3.4 addworker()-新增工作线程

总体来说,addWorker(Runanble firstTask,boolean core) 可以分为两步:

  • 第一步:CAS安全的向线程池添加工作线程:主要是通过双层for循环,外层循环先获取线程池状态,然后校验校验线程池状态;内存循环,先获取线程池worker个数,通过CAS安全设置worker线程个数,如果设置成功,则跳出循环;失败,则校验线程池状态是否发生变化,如果变化,则跳到外层循环,否则,直接走内存循环逻辑。
  • 第二步:创建worker线程,并通过ReentrantLock 将woker线程添加到工作线程集合中去。
private boolean addWorker(Runnable firstTask, boolean core) {  
    retry:  
    // 外层自旋 线程个数加1  
    for (;;) {  
        int c = ctl.get();  
        int rs = runStateOf(c);  
        if (rs >= SHUTDOWN &&  
            ! (rs == SHUTDOWN &&  
               firstTask == null &&  
               ! workQueue.isEmpty()))  
            return false;  
  
        // 内层自旋,主要是以cas的方法增加线程个数  
        for (;;) {  
            int wc = workerCountOf(c);  
            // worker数量超过容量,直接返回false  
            if (wc >= CAPACITY ||  
                wc >= (core ? corePoolSize : maximumPoolSize))  
                return false;  
            // 使用CAS的方式增加worker数量。  
            // 若增加成功,则直接跳出外层循环进入到第二部分  
            if (compareAndIncrementWorkerCount(c))  
                break retry;  
            c = ctl.get();  // Re-read ctl  
            // 线程池状态发生变化,对外层循环进行自旋  
            if (runStateOf(c) != rs)  
                continue retry;  
            // 其他情况,直接内层循环进行自旋即可  
        }   
    }  
    boolean workerStarted = false;  
    boolean workerAdded = false;  
    Worker w = null;  
    try {  
        w = new Worker(firstTask);  
        final Thread t = w.thread;  
        if (t != null) {  
            // 独占锁 保证操作worker集合同步  
            final ReentrantLock mainLock = this.mainLock;  
            // worker的添加必须是串行的,因此需要加锁  
            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)) {  
                    // worker已经调用过了start()方法,则不再创建worker  
                    if (t.isAlive()) // precheck that t is startable  
                        throw new IllegalThreadStateException();  
                    // worker创建并添加到workers成功  
                    workers.add(w);  
                    // 更新`largestPoolSize`变量  
                    int s = workers.size();  
                    if (s > largestPoolSize)  
                        largestPoolSize = s;  
                    workerAdded = true;  
                }  
            } finally {  
                mainLock.unlock();  
            }  
            // 启动worker线程  
            if (workerAdded) {  
                t.start();  
                workerStarted = true;  
            }  
        }  
    } finally {  
        // worker线程启动失败,说明线程池状态发生了变化(关闭操作被执行),需要进行shutdown相关操作  
        if (! workerStarted)  
            addWorkerFailed(w);  
    }  
    return workerStarted;  
}

3.5 工作线程的执行逻辑-runWorker

        通过上面分析我们已经清楚,线程池处理业务的逻辑单元为Worker类,Worker类图如下: image.png

Worker的run方法,会直接调用runWoker方法,下面直接分析runWorker()方法源码:

final void runWorker(Worker w) {  
    Thread wt = Thread.currentThread();  
    Runnable task = w.firstTask;  
    w.firstTask = null;  
    // 调用unlock()是为了让外部可以中断  
    w.unlock(); // allow interrupts  
    // 这个变量用于判断是否进入过自旋(while循环)  
    boolean completedAbruptly = true;  
    try {  
        //  阻塞队列的特性就是:当队列为空时,当前线程会被阻塞等待  
        //  getTask()
        while (task != null || (task = getTask()) != null) {  
            // 这儿对worker进行加锁,是为了达到下面的目的  
            // 1. 降低锁范围,提升性能  
            w.lock();  
            // 如果线程池正在停止,则对当前线程进行中断操作  
            if ((runStateAtLeast(ctl.get(), STOP) ||  
                 (Thread.interrupted() &&  
                  runStateAtLeast(ctl.get(), STOP))) &&  
                !wt.isInterrupted())  
                wt.interrupt();  
            // 执行任务,且在执行前后通过`beforeExecute()`和`afterExecute()`来扩展其功能。  
            // 这两个方法在当前类里面为空实现。  
            try {  
                beforeExecute(wt, task);  
                Throwable thrown = null;  
                try {  
                    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 {  
                // 帮助gc  
                task = null;  
                // 已完成任务数加一   
                w.completedTasks++;  
                w.unlock();  
            }  
        }  
        completedAbruptlyfalse;  
    } finally {  
        // 自旋操作被退出,说明线程池正在结束  
        processWorkerExit(w, completedAbruptly);  
    }  
}

在getTask方法中:

  • 当woker线程超过核心线程数时,会调用阻塞队列的poll(long timeout, TimeUnit unit) 方法,即空闲线程的存活时间(线程池的七大参数)。
  • 如果是核心线程,getTask在获取任务的时候,调用阻塞队列的take()方法,该方法一直会阻塞线程,直到有的任务进入队列,既当线程池空闲时,线程池中的核心线程的状态为block.