ThreadPoolExecutor源码解析

简介:
汗,又拖更了许久,终于续上了。多说无益,show code,让我们继续ThreadPoolExecutor
UML图:
shi

最顶层是Executor,核心也就是execute(Runnable command)了。
先简单介绍一下几个核心参数:

    //线程工厂,设置线程名字,方便定位线程
    private volatile ThreadFactory threadFactory;

    //超过最大线程数量之后执行的饱和策略
     1.AbortPolicy:直接抛出异常。
     2.CallerRunsPolicy:只用调用者所在线程来运行任务。
     3.DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
     4.DiscardPolicy:不处理,丢弃掉。
     5.也可以根据自定义策略。如先持久化,记录日志或者数据库,后续补偿。
    private volatile RejectedExecutionHandler handler;

    //线程空闲时间,超过这个时间,线程自动退出(默认针对超出corePoolSize的线程)
    private volatile long keepAliveTime;

    //是否回收核心线程
    private volatile boolean allowCoreThreadTimeOut;

    //核心线程数量:
    //小于该数量,执行任务时会创建新的线程
    //默认核心线程是不会回收的,当然也可以设置成可回收
    private volatile int corePoolSize;
    
    //最大线程数量(包括线程队列里面的线程数量),达到这个线程数的时候会执行饱和策略
    private volatile int maximumPoolSize;
    
    //线程任务队列
     1.ArrayBlockingQueue:基于数组的有界阻塞队列,此队列有序: FIFO(先进先出)。
     2.LinkedBlockingQueue:基于链表的阻塞队列,此队列有序: 吞吐量通常要高于ArrayBlockingQueue
     3.SynchronousQueue:不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用取出操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue
     4.PriorityBlockingQueue:具有优先级的无限阻塞队列。
    private final BlockingQueue<Runnable> workQueue;

    //默认饱和策略
    private static final RejectedExecutionHandler defaultHandler =
            new ThreadPoolExecutor.AbortPolicy();

比较常见的参数就上面这些,其实知道了上面这些,已经足以应付不少的面试了。
什么?你想怼面试官?那好,我们继续。
在这里插入图片描述
要想怼面试官,源码少不了,让我们开始源码之旅:
同样的,先介绍几个阅读源码之前需要知道的参数:

	//29
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //1 << 29:	00100000000000000000000000000000
    //CAPACITY:00011111111111111111111111111111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
	//-1:	11111111111111111111111111111111
    private static final int RUNNING    = -1 << COUNT_BITS;
    //0:	00000000000000000000000000000000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    //1:	00000000000000000000000000000001
    private static final int STOP       =  1 << COUNT_BITS;
    //2:	00000000000000000000000000000010
    private static final int TIDYING    =  2 << COUNT_BITS;
    //3:	00000000000000000000000000000011
    private static final int TERMINATED =  3 << COUNT_BITS;

除了CAPACITY,其他几个变量代表了线程池的状态:

  • CAPACITY:最大线程数
  • RUNNING: 高3位111(运行状态)
  • SHUTDOWN: 高3位000(此状态不再接收新的任务,但是会继续处理队列中的任务)
  • STOP: 高3位001(此状态不再接收新的任务,也不处理队列中的任务)
  • TIDYING: 高3位010(尝试中止线程池的一个状态变更,可以看作准备中止状态,在线程总数量为0的时候,会进入此状态,同时提供了一个钩子方法terminated())
  • TERMINATED:高3位011(钩子方法terminated()之后进入该状态,彻底中止了)

ctl:

	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
	private static int ctlOf(int rs, int wc) { return rs | wc; }

可以看到ctl其实就是RUNNING,通常ctl与下面几个计算方法绑定的(下面的c就是ctl)

   	//~CAPACITY: 11100000000000000000000000000001
    //return c & ~CAPACITY:获取c的高3位
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    //CAPACITY:00011111111111111111111111111111
    //c & CAPACITY:返回的就是c的低29位
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    //SHUTDOWN是0,小于SHUTDOWN,也就是判断是否是RUNNING
    private static boolean isRunning(int c)	 { return c < SHUTDOWN;}

获取高3位低29位,用来判断线程池的状态和线程总数量。

  • 高3位:判断线程池的状态
  • 低29位:判断线程总数量

看完参数,开始看方法,主要方法就一个:
execute(Runnable command):

    public void execute(Runnable command) {
        if (command == null)
            //不能为null
            throw new NullPointerException();
        int c = ctl.get();
        //返回ctl的低29位,判断是否小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
            //创建核心线程执行任务
            if (addWorker(command, true))
                //成功就返回
                return;
            //失败说明判断的时候小于,添加任务的时候不满足条件了,就重新获取ctl
            c = ctl.get();
        }
        //走到这,说明大于核心线程数,则判断是否是运行状态,是就往队列中加任务
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //如果不是RUNNING,就移除当前任务
            if (! isRunning(recheck) && remove(command))
                //失败就执行拒绝策略
                reject(command);
            //判断线程总数量是否为0(有可能线程执行任务报错了,然后就被销毁了)
            else if (workerCountOf(recheck) == 0)
                //创建非核心线程执行任务(注意这里是null)
                addWorker(null, false);
        }
        //走到这,说明不是运行状态或者队列满了,就创建非核心线程执行任务
        else if (!addWorker(command, false))
            //失败就执行拒绝策略
            reject(command);
    }

总的来说,比较简单,大概流程是这样的:
在这里插入图片描述

addWorker(分为2部分):

1.校验线程池状态和线程总数量:

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
              //获取高3位
            int rs = runStateOf(c);

            //判断状态:
            1.如果>= SHUTDOWN就继续判断
            2.判断== SHUTDOWN,不是就返回false,是就继续判断
            3.== SHUTDOWN,判断firstTask(添加的任务)是否为null,不是就返回false,是就继续
            4.== SHUTDOWN,且添加的任务是null,判断workQueue是否不为null,是就返回false,不是就继续
            //上面的几个判断,总结下来:
            1.rs不是RUNNING,不是SHUTDOWN,就返回false
            2.rs是SHUTDOWN,再判断firstTask == null,表示任务已经加到任务队列了,
            3.最后再判断任务队列是否不为null,不同时满足就返回false,否则就继续往下走
            if (rs >= SHUTDOWN &&
                    ! (rs == SHUTDOWN &&
                            firstTask == null &&
                            ! workQueue.isEmpty()))
                return false;

            for (;;) {
                //判断线程总数量
                int wc = workerCountOf(c);
                //core表示是创建核心线程还是非核心线程
                if (wc >= CAPACITY ||
                        wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //CAS自增线程总数量,失败就回到retry,重新执行for(;;)
                //retry是一个标记,表示跳出retry:标记的循环,正常的break是只跳出当前循环,用retry:标记之后,就可以跳出多个循环了
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();
                //判断状态是否改变了
                if (runStateOf(c) != rs)
                    //相当于continue一次retry:标记的循环,正常的continue是只跳过当前循环的这一次,用retry:标记之后,就可以跳过标记的循环
                    continue retry;
            }
        }

这里主要是校验了线程池状态:

  • SHUTDOWN(不再接收新的任务,但是会继续处理队列中的任务)
  • STOP或者大于SHUTDOWN(不再接收新的任务,也不再处理队列中的任务)
  • CAS自增线程总数量

2.执行任务:

        //走过上面的循环,说明线程池状态和总数量没问题
        boolean workerStarted = false;
        boolean workerAdded = false;
        ThreadPoolExecutor.Worker w = null;
        try {
            //新建一个任务
            w = new ThreadPoolExecutor.Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                //加锁,因为下面的workers是HashSet,线程不安全
                mainLock.lock();
                try {
                    //获取状态
                    int rs = runStateOf(ctl.get());
                    //再次判断状态
                    if (rs < SHUTDOWN ||
                            (rs == SHUTDOWN && firstTask == null)) {
                        //判断线程是否存活
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        //largestPoolSize等于任务数量
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    //解锁
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //开始执行任务的内容
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                //添加进失败任务
                addWorkerFailed(w);
        }
        return workerStarted;
    }

这里很简单,除了一些校验,就是把任务封装成一个Worker,丢到HashSet里面。

我们继续去看真正的执行任务 t.start()
在这里插入图片描述
可以看到,真正执行的是runWorker:

runWorker():

    final void runWorker(ThreadPoolExecutor.Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock();
        boolean completedAbruptly = true;
        try {
            //核心线程和非核心线程传进来的任务都不是null,所以会先执行task
            //执行完了,就队列里面取任务
            while (task != null || (task = getTask()) != null) {
                w.lock();
                //判断线程池状态是否 >= STOP,或者外部调的当前线程的interrupted()中断
                if ((runStateAtLeast(ctl.get(), STOP) ||
                        (Thread.interrupted() &&
                                runStateAtLeast(ctl.get(), STOP))) &&
                        !wt.isInterrupted())
                    //中断当前线程,就不再从队列中取任务了,销毁了
                    wt.interrupt();
                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 {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            //将队列中的任务数量自减
            //注意,走到这,2个条件,task == null,
            //getTask() == null,这个情况就是线程池状态是 >= STOP,或者是SHUTDOWN,队列里面没任务
            processWorkerExit(w, completedAbruptly);
        }
    }

这里就是先执行自身的任务,执行完了就去队列里面取任务。
不过有个小知识,beforeExecuteafterExecute,是可以做一些小动作的,大家随意。

getTask():

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            //线程池状态
            int rs = runStateOf(c);
            //状态是SHUTDOWN,队列里面没任务,就返回null
            //状态>= STOP,就把队列任务数量CAS -1,返回null
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
            //获取线程总数量
            int wc = workerCountOf(c);

            //判断allowCoreThreadTimeOut,默认false,不回收
            //判断是否为核心线程
            //此处是有希望怼面试官的一点,有心的朋友记一下
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            //如果大于最大线程数量,或者allowCoreThreadTimeOut为true,并且非核心线程从队列中取任务,取的是null
            if ((wc > maximumPoolSize || (timed && timedOut))
                    && (wc > 1 || workQueue.isEmpty())) {
                //CAS,自减线程总数量
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                //核心线程不回收的原因,注意timed是可以控制回收的
                //核心线程是take(),线程状态是WAITING ,无限制的等待,非核心线程是有参的poll(),线程状态是TIMED_WAITING,有时间限制的等待
                Runnable r = timed ?
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                        workQueue.take();
                if (r != null)
                    return r;
                //到这一步,说明是非核心线程从队列中取任务,队列中无任务
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

这里的重点其实就是一句话:
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
核心线程不回收,非核心线程回收的原因就在这。
这里也有个小知识,allowCoreThreadTimeOut是可以控制核心线程也能回收的。
如果有朋友对take()poll()不熟悉的朋友可以去翻博主的LinkedBlockingQueue的文章。

到这里,ThreadPoolExecutor的重点基本上就说完了,总结一下2个小知识:

  • beforeExecuteafterExecute,可以做一些小动作
  • allowCoreThreadTimeOut可以控制核心线程回收

下面再稍微看一下不太重要的2个地方:

不太重要的2个地方:

1.tryTerminate():

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            //是RUNNING,或者是TERMINATED,或者(是SHUTDOWN,并且队列不为空)
            if (isRunning(c) ||
                    runStateAtLeast(c, TIDYING) ||
                    (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                //返回不终止
                return;
            //线程总数量不是0
            if (workerCountOf(c) != 0) {
                //终止第一个线程
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                //把状态改完TIDYING
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        //钩子方法,需要自己实现
                        terminated();
                    } finally {
                        //把状态改完TERMINATED
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
        }
    }

是一个尝试中止线程池的方法,一般调用线程池的shutdown()会用到,其主要作用其实就是那个钩子方法terminated(),中止之前可以让你做点小动作。

2.retry:

不知道大家注意到这里面有一个写法retry:,一个跳出多重循环的小技巧,下面给大家演示一下:

在这里插入图片描述

可以看到,此时是只跳出了内部的循环,最外层的循环还是会执行
在这里插入图片描述
可以看到,此时跳出了最外层的循环,用retry:就是可以跳出多个循环
在这里插入图片描述
可以看到,此时只跳出了当前循环的这一次
在这里插入图片描述
可以看到,此时跳出了标记的这次循环的这一次
总结下来就是:

  • breakcontine可以直接到跳到retry:`标记的for循环
    好了,重要的不重要的都看完了,看完这篇,还怼不了面试官,就从了他吧。在这里插入图片描述

下篇文章写ThreadLocal,或者开始写RocketMQ专栏(进军MQ,冲啊)在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值