JUC二

二、线程池:

先来张图了解一下,线程池的实现路线

《JUC二》

       线程池的大BOSS是Executor接口, ExecutorService接口实现了Executor接口,AbstractExecutorService是一个实现了ExecutorService接口的抽象类,终于到了我们的主要内容线程池ThreadPoolExecutor,它是继承了AbstractExecutorService这个抽象类

1、Executor中声明了最核心的方法Execute(Runnable command)方法,

2、ExecutorService实现了Executor接口,并在其中生命了一些其他方法submit()、invokeAll()、invokeAny()、shutDown()

3、AbstractExecutorService()抽象类实现了ExecutorService接口中的所有方法

4、最终ThreadPoolExecutor继承了AbstractExecutorService这个抽象类

ThreadPoolExecutor中有几个重要的方法

1、Execute()

2、submit();这是AbstractExecutorService抽象类中声明的方法,其本质会调用Execute方法

3、shutdown()

4、shutdownNow()

接下来我们分别来介绍一下

       *Execute()实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是线程池ThreadPoolExecutor中的核心方法,通过这个方法可以向线程池提交一个任务,交给线程池来处理

       *submit()是在ExecutorService中声明的一个方法,在AbstractExecutorService中具体实现的,在ThreadPoolExecutor中并为对其进行重写,这个方法也是用于向线程池中提交任务,与Execute()不同的是,这个会返回执行结果(通过Future()来获取执行的结果)。Submit()实质还是调用Execute来实现的

       *shutdown()和shutdownNow():用来关闭线程池

*还有很多其他的方法:比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法

解读线程池流程

       (1)首先需要介绍的是线程池的状态变量runState,它被定义为volatile int类型的,主要是为了实现线程之间的可见性,防止其他线程对当前线程池进行操作,修改了线程池状态,以便其他线程能够及时看到线程池的状态,线程池具体的状态有:

       static final int RUNNING= 0;线程池创建后的初始态,运行态

    static final int SHUTDOWN= 1;调用shutdown()后,线程处于该状态,不在接受新的任务,等待所有任务之行结束

    static final int STOP= 2;调用shutdownNow()后,线程的不再接收新的任务,并会尝试终止当前正在执行的线程

    static final int TERMINATED = 3;当线程池处于shutdownstop状态,并且所有工作已经被销毁,任务队列已经清空或者执行结束,此时的线程池状态为TERMUNATED

       (2)比较重要的一些变量

private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务

private final ReentrantLock mainLock = new ReentrantLock();//线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁

private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集

private volatile long  keepAliveTime;//线程存活时间  

private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间

private volatile int   corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)

private volatile int   maximumPoolSize;//线程池最大能容忍的线程数,即线程补救措施,能够扩充创建的最大线程个数

private volatile int   poolSize; //线程池中当前的线程数

private volatile RejectedExecutionHandler handler;//任务拒绝策略

private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程

private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数

private long completedTaskCount;   //用来记录已经执行完毕的任务个数

    3)任务从提交到执行的流程

《JUC二》

    线程池的核心方法为execute(Runnable command)任务作为参数传入这个方法,(1)首先需要判断提交的任务是否为空,若任务为空则抛出空指针异常,反之,(2)进入下一个if语句块,判断当前线程池中的线程个数是否大于核心线程数,若小于则需要执行与连接符后的语句,addIfUnderCorePoolSize()从字面我们可以解读出,就是当线程池中的数目小于定义的核心线程数,我们就执行添加线程操作,并将任务交给新建的这个线程执行;反之若线程池中线程数目已经达到定义的核心线程数目,则不能在创建,直接进入线程池操作,即下一个if语句块(3)进入线程池后,先判断线程池当前状态是否为运行态,任务是否可以成功添加到任务缓存队列中,如果为true再进入接下来的if语句块;(4)判断线程池状态以及线程池中的线程数目,防止其他线程调用shutdown()shutdownNow()方法关闭线程池,ensureQueueTaskHandled(command)用于保证当前添加进队列的线程得到处理;(5)回退到第(3)步,如果当前线程池不处于运行态或者添加任务缓存队列失败,那么执行else if语句,即addIFUnderMaximumPoolSize(command)函数,其实就是线程池的补救操作,扩大线程池的线程数目,如果当前线程数目已经达到了扩展的最大限度,则线程池拒绝处理该任务,否则扩展线程个数,并将任务交个新建线程

《JUC二》

    接下来我们再说说addIfUnderCorePoolSize(command)函数,他首先会获取锁,再次通过if语句判断当前线程池的状态以及是否可以添加进缓存队列,这是为什么呢,前面不都已经判断过一次了吗?其实仔细想想我们操作的线程池,存在并发现象,即可能第一次判断状态结果还是true,当正要操作时状态就可能发生了改变,所以我们需要再次判断,创建线程执行该任务,若返回true表示创建线程成功,解锁,则调用start()方法,启动线程

《JUC二》

    再来看看addThread(command)方法,用提交任务创建了一个worker对象,用线程工厂创建一个线程,创建线程成功传递给worker对象中的成员变量thread,再通过workers.add(w)worker对象传递到工作集。

《JUC二》

《JUC二》

《JUC二》

    接下来进入worker类,看看他是怎么实现的,worker类实现了Runnable接口,因此上面的Thread t = threadFactory.newThread(w);效果跟下面这句的效果基本一样:它的最核心方法是run()方法,我们可以看到run方法中,执行传递进来的任务,调用runTask()方法,在while循环中不断获取任务进行处理,从哪里获取的呢?task=getTask(),即从任务缓存队列中获取,getTaskThreadPoolExecutor类中的方法

《JUC二》

《JUC二》

    getTask()方法中,先获取线程池当前状态,如果runState大于SHUTDOWN(即为STOP或者TERMINATED),则直接返回null。如果runStateSHUTDOWN或者RUNNING,则从任务缓存队列取任务poll()。如果当前线程池中线程数目大于核心线程池数目或者允许为线程池中线程设置空闲存活时间,则调用poll(time,timeUnit),这个方法会等待一定的时间,超过这个时间则会返回null。接下来判断获取的任务是否为空,若为null,则调用workerCanExit()方法来判断当前worker是否可以退出

《JUC二》

    也就是说如果当前线程池处于STOP状态,或者缓存队列为空或者允许为线程池设置空闲存活时间并且线程存活数目大于1,允许worker退出,则调用InterruptldleWorkers()中断处于空闲状态的worker

《JUC二》

《JUC二》

    我们可以看出其实质是调用Worker对象的Interruptlfdle()方法,在这个方法中通过tryLock()来获取锁的,因为若当前worker如果处于运行状态已经获取了锁,是无法再获取锁的

       *这里有一个非常巧妙的设计方式,假如我们来设计线程池,可能会有一个任务分派线程,当发现有线程空闲时,就从任务缓存队列中取一个任务交给空闲线程执行。但是在这里,并没有采用这样的方式,因为这样会要额外地对任务分派线程进行管理,无形地会增加难度和复杂度,这里直接让执行完任务的线程去任务缓存队列里面取任务来执行。

addIfUnderMaximumPoolSize方法与addIfUnderCorePoolSize方法的实现思想非常相似,只是if语句判断条件中的poolSize < maximumPoolSize不同而已。

最后看一个线程池示例:

《JUC二》

《JUC二》

 

    原文作者:JUC
    原文地址: https://blog.csdn.net/sinat_36722750/article/details/82106968
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞