线程池工作原理和源码解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yhl_jxy/article/details/82796399

线程池相信大家都用过,定义一个线程池,然后就是往线程池提交任务,最后线程池就执行任务。

这里面有两个问题需要探讨:

1)提交任务后,线程池如何执行任务?

2)线程池线程如何重复利用核心线程执行随时提交的任务?

下面咱们针对这两个问题进行分析。

一 线程池创建

在Java 并发包下,提供了Executors工厂创建线程池,但是一般不建议调用工厂方法创建线程池,

建议自定义线程池。调用线程工厂创建线程池或自定义线程池最后都逃不过调用

ThreadPoolExecutor的宿命,看下ThreadPoolExecutor构造方法的源代码:

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

corePoolSize:线程池的核心线程数。每一次任务的提交只要当前线程池已创建线程数小于核心线程数,都会创建线程执行任务,

无论你当前线程池是否有空闲线程。这些线程是全职工,即便没有任务执行,他们也会等待任务。

也即,当线程池里没有任务任务时,也会有corePoolSize个忠诚的线程在等着执行任务。

maximumPoolSize:线程池最大线程池数。无论你提交多少任务,线程池里最多能创建maximumPoolSize个工作线程。

keepAliveTime:线程的存活时间。这个存活时间控制的是大于corePoolSize之后创建的线程,如果这部分线程

等待keepAliveTime时间后,还没任务处理,哪么这部分线程就会退出线程池,可以比喻成线程池的临时工,就像饭店一样,

生意火的时候,全职的员工忙不过来,找了些临时工来帮忙,当没啥事干的时候,这部分临时工就辞退了,就像这部分线程

退出了一样。

unit:指定keepAliveTime的单位。比如,TimeUnit.SECONDS,表示单位为秒。

workQueue:阻塞队列。当核心线程处理不过来的时候,任务就放入队列里面等待执行。

threadFactory:线程工厂。用于创建线程池线程。

handler:饱和策略。当线程池达到饱和之后,提交的任务交给饱和策略处理。

二 线程池工作原理

线程池工作原理图:

1)任务提交到线程池,首先判断线程数是否小于corePoolSize,如果小于,则创建线程执行任务,

      无论是否有空闲线程。

2)如果核心线程已满,判断阻塞队列是否已满,如果未满,则将任务加入队列等待执行。

3)如果阻塞队列已满,判断线程池数是否小于maximumPoolSize,如果小于,创建线程执行任务。

4)如果线程池已满,提交的任务交由饱和策略处理(handler)。

下面基于jdk1.8.0_181,从源码层面分析线程池工作原理。

1、线程池提交任务的execute()方法

源码位置:java.util.concurrent.ThreadPoolExecutor#execute

这四个步骤对应上图线程池工作原理图。

1)任务提交到线程池,首先判断线程池中当前工作线程数是否小于corePoolSize,如果小于,

则调用addWorker()创建线程执行任务。

2)如果线程池线程数不小于corePoolSize,调用阻塞队列workQueue.offer()往队列添加任务等待执行。

3)如果放入队列失败,第三步会去创建线程执行任务,在内部判断了如果线程池线程数

不小于maximumPoolSize,则创建失败,说明这个时候线程池已经达到饱和状态。

4)线程池达到饱和状态后,交由饱和策略处理,即reject()方法处理。

2、addWorker()方法

上面相当于线程池工作原理的流程概要,具体线程的创建和执行由addWorker()方法完成。

源码路径:java.util.concurrent.ThreadPoolExecutor#addWorker

addWorker方法太长,咱们分两部分分析。

    这个方法接受一个boolean类型的core变量,如果大家细心的话,在execute()源码中线程数

小于corePoolSize时,调用addWorker()时传进来的是true,队列满了后再调时传的是false。

这个boolean的core取决于线程池线程数是与corePoolSize比较大小,还是与maximumPoolSize

比较大小,来决定能否创建相应的线程。

  如果core是true,这个是时候是判断是否能创建核心线程,如果corePoolSize小于线程池数量,

则能创建,否则不能创建核心线程;

  如果core是false,这个时候是判断是否能再创建线程,如果maximumPoolSize小于线程池数量,

则能创建,否则不能创建非核心线程。

1)这个地方是创建工作线程的地方,返回一个Worker然后从中获取Thread实例。

2)看到这个start()方法,是不是有一种久违的感觉,咱们从提交任务到线程池,一路走来,

终于看到了线程启动的方法。

3、Worker的结构

Worker继承了AQS框架,同时实现了Runnable接口,让Worker拥有了同步和线程的特性。

1)创建线程,线程工厂传进去的是this,也就是Worker,而不是我们提交的任务,

我们提交到线程池的任务被赋值为firstTask,成为了Worker的一个成员属性。

2)我们上面已经启动了线程,线程最终会执行对应启动线程的run()方法,Worker里面的run()方法

调用了runWorker方法,在runWorker里面才是真正执行我们提交的任务的地方。

4、runWorker逻辑

1)从Worker取成员变量firstTask,即为我们提交到线程池的任务。

2)while写了个死循环,如果task不为空,或者getTask()从队列里面能取到任务,while就不会退出。

3)task调用run方法,task为我们提交的任务,终于执行了我们提交的任务里面的run方法。

到这里,我们对线程池的工作原理有了一个基本的认识,但是还有一个问题就是线程池不提交任务时,

线程池的核心线程如何维持不退出一直等待获取任务执行,是因为它长得帅吗,超出核心线程数创建的哪些

线程什么时候退出了,是因为害羞了吗?

要理解这两个问题,还需要结合getTask(),看里面都干了啥。

5、getTask源码

1)如果线程池关闭了同时任务状态为Stop或队列为空时,线程数通过CAS算法减少,外层的while退出,线程退出。

2)boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

返回timed是一个boolean值。allowCoreThreadTimeOut默认为false,如果wc > corePoolSize,

也即线程池线程数大于核心线程数时,从判断条件可以知道,当队列为空,

并且TimeOut为True的时候,调用compareAndDecrementWorkerCount(c)

减少线程池数,同时,返回null,外层while循环终止,线程释放,退出的是非核心线程。

这里需要注意的是TimeOut默认是false,什么时候变成True,在下面分析。

3)如果timed为true,是非核心线程通过poll从队列获取任务,超时时间为keepAliveTime,

如果获取不到任务,TimeOut就会被设置为True,因为代码在for死循环里面,会触发上面非核心

线程退出的代码块。

    如果timed为false,是核心线程从队列中通过take获取任务,如果没有任务,则阻塞,

这就保证了在线程池没有任务的时候,corePoolSize数量的核心线程不退出线程,

一直等待任务的执行,因为take拿不到值,就一直阻塞,这就是线程池没有任务执行时

还能维持核心线程不退出线程池的本质。

    因为核心线程数量的线程一直等待任务的执行,这个时候如果再往线程池提交任务,

是不会创建线程的,而是任务直接加入队列,阻塞的核心线程就会通过take从队列拿到任务,

执行任务,没有任务了,核心线程又恢复阻塞。

我们通过上述了解,应该能解决下面三个问题:

1)线程池的工作原理?

2)非核心线程如何退出?

3)核心线程如何维持不退出?

猜你喜欢

转载自blog.csdn.net/yhl_jxy/article/details/82796399