看完这一篇,再也不怕面试官问Java线程池

509 阅读6分钟

今天来介绍一下面试几乎是必问的内容:线程池,那么在Java,JUC并发工具包中提供了Executors的工具类来操作线程池,而它实际就是使用的ThreadPoolExecutor,那下面就来从源码角度分析一下ThreadPoolExecutor。

属性

  • ctl

    ctl是AtomicInteger类型的数值,它用来表示线程池的控制状态,ctl的值存储了两个不同的状态值:工作线程数和线程状态。ctl的前三位用来表示线程状态,后29位用来表示工作线程数

    ![image-20200409151836030](/Users/zhangminglei/Library/Application Support/typora-user-images/image-20200409151836030.png)

  • 线程运行状态

    RUNNING = -1 << COUNT_BITS(11100000 ... ... 00000000)

    SHUTDOWN = 0 << COUNT_BITS(00000000 ... ... 00000000)

    STOP = 1 << COUNT_BITS(00100000 ... ... 00000000)

    TIDYING = 2 << COUNT_BITS(01000000 ... ... 00000000)

    TERMINATED = 3 << COUNT_BITS(01100000 ... ... 00000000)

  • 其他属性值

    COUNT_BITS = Integer.SIZE - 3(29)

    CAPACITY = (1 << COUNT_BITS) - 1(2^29-1),用二进制表示:0001111 11111111 11111111 11111111

工作线程数目、线程状态以及ctl值通过下面的这三个方法来计算出相应的值

private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

构造函数

ThreadPoolExecutor的参数有哪些也是面试中经常问的题目,下面我们就拿参数最多的构造函数来介绍一下。

corePoolSize:线程池核心池数目,即使线程是空闲的,也不会被回收(allowCoreThreadTimeOut默认为false,如果设置为true,则核心池空闲时,会被回收)

maximumPoolSize:线程池中允许的最大线程数目

keepAliveTime:大于核心池的线程空闲时,能在线程池中存在的最大时间

unit:keepAliveTime的时间单位

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.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;
}

拒绝策略

jdk自带实现了4种拒绝策略。

  • AbortPolicy

    直接抛出异常RejectedExecutionException

  • CallerRunsPolicy

    由调用者来执行本次任务

  • DiscardPolicy

    直接丢弃任务,什么都不处理

  • DiscardOldestPolicy

    丢弃最旧的任务,再去execute本次任务

execute方法

首先来看execute方法执行的三个步骤,然后再去看一下它的源码,详细分析线程池的实现原理。

  1. 如果线程池中运行的线程数目小于corePoolSize,尝试启动新的线程来执行任务
  2. 如果任务能成功加入到队列中,仍然需要二次检查看是否需要创建一个线程(因为存在的一个线程可能在上次检查之后已经停止了)或者线程池已经shutdown了。因此再次检查这个状态,并且如果线程池停止了就需要把刚才入队的任务出队,或者启动一个新的线程
  3. 如果任务加入队列失败,那么就去尝试添加一个新线程,如果失败,就去执行拒绝策略。
public void execute(Runnable command) {
    //任务command不能为空
    if (command == null)
        throw new NullPointerException();
    //获取ctl值
    int c = ctl.get();
    //第一步:小于核心池数目,创建线程执行任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 第二步:大于核心池数目,则把任务加到队列中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 第三步:队列满了,则去创建线程执行任务,线程数要小于最大线程数目,否则执行拒绝策略
    else if (!addWorker(command, false))
        reject(command);
}

从execute方法源码也可以看出执行的三个步骤,下面来看一下addWorker方法,这个方法主要是创建线程,执行任务。

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get(), rs = runStateOf(c);
        // 这个判断的意思是:线程池状态大于SHUTDOWN的话,不再执行任务;
        // 等于SHUTDOWN的话,不接收新任务,队列中还有任务的话继续执行
        if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
            return false;
        for (;;) {
            int wc = workerCountOf(c);
            // 判断是否超过线程池数目要求
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 线程池工作数目+1
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            //更新线程池工作数目失败的话,自旋继续更新
            if (runStateOf(c) != rs)
                continue retry;
        }
    }

    boolean workerStarted = false, workerAdded = false;
    Worker w = null;
    try {
        // 创建新worker,Worker类实现了Runnable接口,所以worker里面有需要执行的任务
        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());
                //再次检查线程池运行状态,并且要求线程池是RUNNING状态,或者SHUTDOWN状态且新任务为null才可以
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // t是刚创建出来的worker的thread,还未启动就是alive的话抛出异常
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                //启动新创建出来的线程,t.start之后回去调用worker里面的run方法,
                //下面会详细解析worker的run方法
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        //失败的话,做回滚操作
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorker中会加入新线程,并启动线程,大家都知道线程启动后回去调用Runnable的run方法,这里也一样,会去调用Worker的run方法(Worker的run方法调用了runWorker方法,所以我们可以直接看runWorker方法的逻辑。省略了一些代码,只看主要的逻辑)

final void runWorker(Worker w) {
    ...
    w.unlock(); 
    try {
        //这里判断如果w.firstTask不为null,则去直接执行这个task
        //如果w.firstTask为null,则通过getTask方法去队列中取任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            ...
            try {
                beforeExecute(wt, task);
                try {
                    //执行任务的run方法
                    //执行任务的前后可以自定义一些执行逻辑,beforeExecute和afterExecute
                    task.run();
                } catch (...) {
                    ...
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        //在最后的总结中会有这个方法的流程图
        processWorkerExit(w, completedAbruptly);
    }
}

再来看一下从队列中取任务的getTask的逻辑(省略了一些代码,只看主要的逻辑)

private Runnable getTask() {
    boolean timedOut = false; 
    for (;;) {
        ...
        int wc = workerCountOf(c);
        //如果允许核心线程超时回收或者worker数目大于核心线程数目,为true
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        ...
        try {
            //如果线程池中的线程需要超时回收的,则以poll的方式从队列取任务,超时返回null
            //否则以take阻塞的方式从队列取任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

总结

ThreadPoolExecutor的主要逻辑基本上都介绍了,最后整理了ThreadPoolExecutor的execute方法和processWorkerExit的流程图,大家可以参考。