ThreadPoolExcutor源码个人浅析(二)


ThreadPoolExcutor源码个人浅析(二)

一、源码浅析

(1)构造方法

ThreadPoolExcutor共有四种构造方法,分别为:

  • ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue)
  • ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory)
  • ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler)
  • ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

以下将结合代码和注释一一分析:

1、ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue)

这个构造方法里的几个参数分别对应:

  • corePoolSize->核心线程数
  • maximumPoolSize->最大线程数
  • keepAliveTime->注释是说:when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating。当线程数大于核心时,这是多余的空闲线程在终止前等待新任务的最长时间。
  • unit->keepAliveTime参数的时间单位
  • workQueue->线程池的缓冲队列

接下来看代码:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}

很普通地初始化一个线程池,没什么好说的。需要注意的是,里面的Executors.defaultThreadFactory()是初始化用来创建新线程的一个线程,至于defaultHandler,它的注释是说:the default rejected execution handle,默认被拒绝的执行句柄,有点抽象,后面的方法会再提到。

2、ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory)

相较于第一个方法多了threadFactory参数,这个参数的注释是这样描述的:the factory to use when the executor creates a new thread,用于执行新线程的创建操作。也就是说作为Executors.defaultThreadFactory()的替代品。再看下代码:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);
}

果然如此,就是替代Executors.defaultThreadFactory()的。

3、ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler)

相较于第一个方法多了handler参数,看下它的注释:the handler to use when execution is blocked because the thread bounds and queue capacities are reached,执行被阻止时要使用的处理程序
因为达到了线程边界和队列容量。可以理解为,线程池满了时,或者说线程数量大于最大线程数时,就会调用这个handler方法了。再看下代码:

    public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}

这明显就是defaultHandler的替代品嘛,那既然如此也就知道defaultHandler是干嘛的了。就是给定一个默认的handler,在线程池被占满,不再开启新的任务时调用的方法。也就是一个拒绝策略。

ThreadPoolExecutor自己已经提供了四个拒绝策略,分别是:

  • CallerRunsPolicy,
  • AbortPolicy,
  • DiscardPolicy,
  • DiscardOldestPolicy。

下面大概描述下这四个拒绝策略:

  • AbortPolicy:ThreadPoolExecutor中默认的拒绝策略就是AbortPolicy。处理方式简单粗暴,直接抛出个RejectedExecutionException异常,也不执行这个任务了。
  • CallerRunsPolicy:在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务。也就是用当前线程池所在的线程去run被拒绝的任务。
  • DiscardPolicy:额,这个拒绝策略啥都不做。。。也就是说,使用这个策略的话,任务被拒绝时不会抛异常也不会被启动。
  • DiscardOldestPolicy:当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。毕竟是队列嘛,先进先出,调个getQueue().poll()下位置就空出来了。

这几个策略的实现也就那样,不会很复杂。有需要的话,自己写一个也成,去实现下RejectedExecutionHandler 接口就可以了。

4、ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

在解读了前三个构造方法之后,这个方法就没什么可说的了,就是在第一个构造方法的基础上加了threadFactory和handler,自己给创建新线程的线程和拒绝策略。

(2)ThreadPoolExcutor常用方法

下面将来大致了解下几个常用的方法:

1、execute(Runnable command)。这个方法顾名思义就是要用来执行线程的。先来他的注释:

Executes the given task sometime in the future.The task may execute in a new thread or in an existing pooled thread.If the task cannot be submitted for execution, either because this executor has been shutdown or because its capacity has been reached,the task is handled by the current {@code RejectedExecutionHandler}.

大意就是如果当前的excutor执行器还没关,并且容量还没满,这个任务就在新线程或者已有的线程池中执行,否则就调拒绝策略的handler。看完应该能理解这东东能干啥用,并且什么条件下会调拒绝策略了。接下来看代码:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    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);
}

里面先做了个入参非空判断,没啥好说的。此外,还贴心地加了一大段注释,告诉我们整个过程分三步:

  1. 若运行的线程少于核心线程数(CorePoolSize),就用给定的命令作为第一个线程启动线程任务。然后addWorker这个东东可以自动检查runState和workerCount,出问题时返回false作为错误警报,用来防止在不该添加时加了线程;
  2. 如果新任务可以成功排到队列里了,我们还是得再检查下,确认我们该不该加这个任务,因为上次检查存活的线程可能检查后就挂了或者说不定线程池都被关闭了。所以再做个检查,如果真的那么不幸,有必要的话就回滚再重新排队,或者要是没有线程的话,再重新启个新的;
  3. 如果连排队都排不了,那就试试再新加个线程来排队,还是不行,那说明这个线程池要么是满了要么是被关了,那就调拒绝策略,把任务拒绝掉。

这些注释已经说得很详细了,代码就不作解析了。

2、shutdown()。这方法是用来将线程池中的线程按顺序一个一个关掉的。不过有几个前提:

1)任务已经执行了,并且不会再接受新的任务;

2)当线程池已经被shut down了,这个调用就没啥效果了。

并且,这个方法不会去等原来执行中的任务执行完成(不过看网上资料都是说会等shutdown前的线程执行结束,和这个注释有点冲突,有点摸不着头脑,后面看代码吧),awaitTermination方法才会。接下来看看代码实现:

 public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

可以看到,在一开始加了一个可重入锁(毕竟是多线程),然后依次执行:

  1. 检查下线程有没权限关,确保可以中断每个worker(工作线程);

  2. 设置线程池的目标状态,如果线程池当前的状态为目标状态或大于目标状态(状态里的大小排序见上一篇)就保持不变;

  3. 调interruptIdleWorkers方法中断线程,这个较复杂,先不展开,总之这个方法就是:中断那些可能在等待执行任务的线程(没有被锁住的——idle语义,tryLock成功,如果Worker线程在执行任务,runWorker方法中的执行任务的Worker是占有的锁的,所以这里是无法获取锁的,也就是非idle的了),让他们能检查是否可以terminate。这里直接吞了SecurityException异常,防止某些线程在interrupt之后仍然处于uninterrupted状态。
    interruptIdleWorkers方法加了个判断if (!t.isInterrupted() && w.tryLock()),interruptIdleWorkers方法会因为这层保护而放弃对某个Worker线程的中断(tryLock为false),也就是说,如果没有持有worker锁,就不会被中断。

  4. onShutdown方法没什么好说的,就是一个钩子,在ScheduledThreadPoolExecutor中有实现,可以在shutdown()时做一些操作;

  5. tryTerminate()这个方法执行过程也比较复杂,先不展开。只要知道调这个方法的执行流程大致是:

    1、判断线程池是否需要进入终止流程(只有当shutdown状态+workQueue.isEmpty 或 stop状态,才需要)

    2、判断线程池中是否还有线程,有则 interruptIdleWorkers(ONLY_ONE) 尝试中断一个空闲线程(正是这个逻辑可以再次发出中断信号,中断阻塞在获取任务的线程)

    3、如果状态是SHUTDOWN,workQueue也为空了,正在运行的worker也没有了,开始terminated会先上锁,将线程池置为tidying状态,之后调用需子类实现的 terminated(),最后线程池置为terminated状态,并唤醒所有等待线程池终止这个Condition的线程

  6. 在最后再在finally里把可重入锁释放掉。

事实上,源码的注释和网上查的资料对于shutdown()方法的描述都没错,不过讲得太绝对,一个说不会等待线程执行完,一个说会。其实是对于没有持有worker锁的运行中的线程会等它执行完,持有worker锁的空闲线程就中断它。大多数资料又没有分析透彻,让人看了摸不着头脑。(我个人描述不是很准确,这篇文章讲得蛮好的,可以再看下这里:https://www.cnblogs.com/trust-freedom/p/6693601.html

3、shutdownNow() 。这个方法和shutdown()基本上差不多,区别只是在于:

  1. 不同于shutdown()第2步将线程池更新为SHUTDOWN状态,它是将线程池更新为STOP状态;
  2. 第3步中断所有线程调用的是interruptWorkers()方法,这个方法不同于interruptIdleWorkers()方法,它是不管三七二十一,终止所有线程。不过可能存在对于运行中的线程调用Thread.interrupt()中断线程,并没有使线程立即被终止,在task.run内部可能捕获了InterruptException,但是它不会被抛出来,使得线程一直没法结束;
  3. 不同于shutdown()的第4步,调将workQueue中待处理的任务移到一个List中,并在方法最后返回,说明shutdownNow()后不会再处理workQueue中的任务。

总而言之,就是这个方法会强硬的终止线程。

4、awaitTermination()。这个方法在前面其他方法的注释中有提到,在shutdown之后调用。设置一个超时时间,当超时时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。这方法也没有注释,直接看代码:

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (;;) {
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            if (nanos <= 0)
                return false;
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
        mainLock.unlock();
    }
}

awaitTermination() 循环的判断线程池是否terminated终止 或 是否已经超过超时时间,然后通过termination这个Condition阻塞等待一段时间。
termination.awaitNanos() 是通过 LockSupport.parkNanos(this, nanosTimeout)实现的阻塞等待。

在发出一个shutdown请求后,在以下3种情况发生之前,awaitTermination()都会被阻塞

  1. 所有任务完成执行。如果发生了 termination.signalAll()(内部实现是 LockSupport.unpark())会唤醒阻塞等待,且由于ThreadPoolExecutor只有在 tryTerminated()尝试终止线程池成功,将线程池更新为terminated状态后才会signalAll(),故awaitTermination()再次判断状态会return true退出;
  2. 到达超时时间。如果达到了超时时间 termination.awaitNanos() 也会返回,此时nano==0,再次循环判断return false,等待线程池终止失败;
  3. 当前线程被中断。如果当前线程被 Thread.interrupt(),termination.awaitNanos()会上抛InterruptException,awaitTermination()继续上抛给调用线程,会以异常的形式解除阻塞。

本篇暂时先分析这些,其他的方法后面再讲。

参考内容来源:

https://blog.csdn.net/qq_25806863/article/details/71172823

https://blog.csdn.net/xxcupid/article/details/51993235

https://www.cnblogs.com/trust-freedom/p/6693601.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值