【笑傲江湖】【JUC】Java线程池

Java中,有一个并发工具包,在java.util.concurrent中,此包主要分有四个模块:原子操作类,Lock锁,并发容器,线程池。本篇笔记博客暂且只记录线程池的部分内容。

目录

 

开局一张图  内容全靠编

线程池框架模型

workQueue的类型有三种

handler的策略有四种

线程池原型

线程池原理

实现

参考


开局一张图  内容全靠编

【笑傲江湖】【JUC】Java线程池_第1张图片

这张图可以看到线程池的继承关系,其中关键的类为:ThreadPoolExecutor。

线程池框架模型

有两个路线,先说第一个路线。另一个是延迟线程池,在实现一节中有介绍。

涉及到Executor、ExecutorService、AbstractExecutorService、ThreadPoolExecutor。

上面那张图循序就是继承顺序

Executor顶层接口,只定义了void execute(Runnable command)方法。

ExecutorService,第二层接口,继承顶级接口,并且新增了好多方法,有终止的,执行的,还有接收返回值的,判断状态的。

AbstractExecutorService,抽象类,实现了ExecutorService,重构了里面的所有方法。

ThreadPoolExecutor,具体类,继承AbstractExecutorService,有了自己的想法,还重写了一些父类的方法,终极实现。

主类就是终极实现类,ThreadPoolExecutor。

ThreadPoolExecutor,线程执行者,负责创建线程,停止线程,各种操作线程。

提供四个构造函数,但是大同小异,只因为参数个数不一样,其中有三个构造是调用另一个,构造的参数

 ThreadPoolExecutor(int corePoolSize,    //核心线程池的数量
    int maximumPoolSize,                 //线程池最大数量
    long keepAliveTime,                  //表示某个线程多久没有操作之后终止
    TimeUnit unit,                       //时间单位,keepAliveTime的单位
    BlockingQueue workQueue,   //阻塞队列,某种队列,池满了要放这里面
    ThreadFactory threadFactory,         //线程工厂
    RejectedExecutionHandler handler);   //饱和策略,各种都满了的时候的策略

构造函数基本都是对类中的属性赋值,还有参数之外的变量进行初始化。

corePoolSize和maximumPoolSize的关系,corePoolSize是线程池的大小,而maximumPoolSize则是可以扩展到多大。

提供操作线程池和队列的方法,主要方法有execute、submit、shutdown、shutdownNow

了解execute、submit区别,submit是可以带有返回值的,通过Future进行接收。

了解shutdown、shutdownNow区别,shutdown并不会立即终止,而是等任务完事了和队列也执行完了,不过不会接受新任务。shutdownNow暴力停止,清空队列,停止当前所有任务,返回未执行的任务。

workQueue的类型有三种

ArrayBlockingQueue:FIFO,需指定大小。

LinkedBlockingQueue:FIFO,不用指定大小,为int类型最大值。

SynchronousQueue:不保存任务,进来任务直接新建线程去执行。

handler的策略有四种

AbortPolicy:丢弃任务,并且抛异常。(默认)。

CallerRunPolicy:由调用线程处理该任务。

DiscardOldestPolicy:丢弃队列中最先进来的一个请求,循环操作。

DiscardPolicy:直接丢弃,不做任何处理。

线程池原型

池是一个集合,这个集合就是Set,而内部类Worker则是具体线程,在Worker中封装了Thread。Set即是线程池。

线程池原理

长篇代码我就不粘贴了,没什么用,我们的jdk版本可能不一样,希望大家看这原理的时候,打开juc下面的源码,对比一下。

阅读主类ThreadPoolExecutor源码,其中execute、submit为增加线程并且执行,但是submit中也是调用了execute。

execute方法调用addWorker方法,addWorker方法操作Worker类,Worker类提供装载线程等操作。

execute方法步骤

1、先于核心线程池长度对比,就是corePoolSize,如果小于,则直接分配线程执行,如果大于,则2。

2、证明corePoolSize满了,那么进队列,如果进队列成功了,则等着执行,没有进入队列,则3。

3、判断和maximumPoolSize的大小,如果还能创建线程,则创建线程,说白了就是扩展线程池大小。

4、如果3的maximumPoolSize已经到头了,就是线程池满了,队列也满了,扩展也满了,就饱和策略了。

其中在execute中,判断分配线程是否成功靠的是addWorker方法,addWorker方法返回boolean:

1、判断当前线程状态,是否可以在新增线程,如果状态不是运行中,则false,可以则2。

2、判断线程池的数量是否超出,没有超出,先把线程池的数量+1。

3、Lock锁住全局,向Set中add一个worker实例。

4、添加到set中解锁Lock,在确保set确实add进去之后,调用worker的start方法启动线程。

5、如果在Set中add没有成功,那么会删除Set中的这个worker实例。

PS:调用addWorker方法的入参addWorker(Runnable firstTask, boolean core)。

         Runnable firstTask是可以为null的,表示没有操作,只创建线程,没有具体的执行逻辑。

         boolean core则是根据核心数量判断还是根据扩展数量判断。

Worker类是关键,表示线程池的单位:

实现Runnable接口,可单独作为一个线程。

final Thread thread;

Runnable firstTask;

提供run方法,在addWorker方法中调用start方法,会执行此run:

1、初始化的时候firstTask不为空,就处理这个。

2、然后循环阻塞队列,去队列里面拿firstTask看看有没有值,如果都没有,退出方法。

3、退出之前,还要判断之前那个keepAliveTime参数。

PS:在addWorker中,会传addWorker(null, false/true),这时候null就起到作用了,证明了上面1的步骤。

         如果addWorker(null, true),则表示是初始化线程池,只把线程创建,而没有具体执行。

         preStartAllCoreThreads方法其实就是调用了addWorker(null, true),线程池会提前创建并启动所有核心线程。

之前迷惑的地方现在解开了

线程池何时调用start方法。

线程池怎么提前创建线程。

addWorker方法的实现步骤。

总结

execute方法步骤就是线程池启动线程的原理。

shutdown、shutdownNow就是停止线程的原理。

addWorker方法就是线程池的线程如何创建的原理。

Worker类是线程池具体表现的模型和原理。

 

实现

此图是创建线程的4中方法。

其中在线程池有N多种线程池的声明,其中底层均是调用以下代码进行返回。

return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());

看图可以看到其中ScheduledExecutorService类,并非之前所说的ThreadPoolExecutor,但是这个ScheduledThreadPoolExecutor类却继承了ThreadPoolExecutor。

在创建ScheduledThreadPoolExecutor时候,调用了父类的构造,也就是之前一直说的ThreadPoolExecutor的构造。

只不过ScheduledThreadPoolExecutor扩展了一些自己的实现,可以定时和周期性的执行任务。

使用线程池,要明确一下用哪种阻塞队列,用哪种饱和策略,都关系到系统的稳定和安全。

参考

https://www.cnblogs.com/superfj/p/7544971.html

https://blog.csdn.net/xiaoxufox/article/details/52278508

有两篇大牛的博客,可供大家参考。

你可能感兴趣的:(并发编程)