Java中,有一个并发工具包,在java.util.concurrent中,此包主要分有四个模块:原子操作类,Lock锁,并发容器,线程池。本篇笔记博客暂且只记录线程池的部分内容。
目录
开局一张图 内容全靠编
线程池框架模型
workQueue的类型有三种
handler的策略有四种
线程池原型
线程池原理
实现
参考
这张图可以看到线程池的继承关系,其中关键的类为: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暴力停止,清空队列,停止当前所有任务,返回未执行的任务。
ArrayBlockingQueue:FIFO,需指定大小。
LinkedBlockingQueue:FIFO,不用指定大小,为int类型最大值。
SynchronousQueue:不保存任务,进来任务直接新建线程去执行。
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
有两篇大牛的博客,可供大家参考。