java线程池学习

urlyy

参考博客:
线程池ThreadPoolExecutor——基础分析!
线程池之——ThreadPoolExecutor源码深度解析
线程池(ThreadPoolExecutor)源码解析
拆解ThreadPoolExecutor之execute方法的各个细节?
ReentrantLock 锁详解
拆解ThreadPoolExecutor之addWorker方法的各个细节?
拆解ThreadPoolExecutor之runWorker方法的各个细节?
拆解ThreadPoolExecutor之关闭线程池和钩子函数方法的各个细节

内部结构

在这里插入图片描述

线程池体系的类图

在这里插入图片描述

Executor系列类

实现了主要的方法,即提交、执行、暂停、状态

在这里插入图片描述

细说ThreadPoolExecutor

主要属性

核心线程数 corePoolSize

线程池的最小容量。

最大线程数 maximumPoolSize

线程池的最大容量。

keepAliveTime

如果池当前线程数大于核心线程数的话,空闲线程的存活时间

threadFactory

创建线程的工厂

workQueue

阻塞队列,用于存储待执行的任务,相当于一个缓冲
常见阻塞队列

名称底层实现描述
ArrayBlockingQueue数组有界
LinkedBlockingQueue链表无界
PriorityBlockingQueue无界
DelayQueuePriorityBlockingQueue具体化了一个PriorityBlockingQueue,排序方式为按时间排序
SynchronousQueue只能容纳一个元素。内部有元素时,试图插入第二个元素的线程会阻塞,直到第一个元素被取走;内部为空时,试图取走元素的线程会阻塞,直到有元素被放入

拒绝策略

在这里插入图片描述

策略是否执行新任务其他操作
AbortPolicy拒绝抛异常
DiscardPolicy拒绝
DiscardOldestPolicy执行丢弃下一个任务(即队头任务)
CallerRunsPolicy执行

按顺序放的源码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意这里是用调用线程执行的这个任务
在这里插入图片描述

一些总结

1、提交任务经过的流程
这个是我看着源码画的
在这里插入图片描述

2、线程池状态

状态描述
RUNNING接受新任务、处理队列中的任务
SHUTDOWN拒绝新任务、处理队列中的任务
STOP拒绝新任务、抛弃队列中的任务、中断正在处理的任务
TIDYING队列为空、任务都执行,即将TERMINATED
TERMINATED终止

在这里插入图片描述

内置线程池

通过Excutors类的静态方法获取这些池,源码里说推荐使用这几个,因为它们更方便,而且适配了常见使用场景(但我看到也有观点推荐用ThreadPoolExcutor自定义)。其实就是对线程池参数做了一些限定,形成了具有某种特征的池子
在这里插入图片描述

newFixedThreadPool

核心线程数==最大线程数、阻塞队列大小为INT_MAX,keeyAliveTime=0
描述:无界队列、固定线程数量、限制最大线程数量、适用于负载较重的场景
在这里插入图片描述

newSingleThreadExecutor

比起newFixedThreadPool,不同在于1 == 核心线程数 == 最大线程数
描述:单线程串行,保证任务按顺序执行
在这里插入图片描述

newCachedThreadPool

核心线程数=0、最大线程数为INT_MAX、阻塞队列为同步队列、keeyAliveTime=60
描述:按需创建、队列中只能有一个任务,因此该任务会被马上执行、线程最多空闲60s(自动回收线程)。适用于任务量小、任务执行时间短的场景。
在这里插入图片描述

newScheduledThreadPool

阻塞队列为DelayedWorkQueue
描述:支持定时和周期任务
在这里插入图片描述

源码剖析

ctl

这个东西在代码里频繁出现
其实就是就是用一个整数(默认为AtomicInteger)存储了线程池的状态以及线程数,带了位运算的思想。
注意只有Running状态,其实就是为了好进行判断,直接runState<0或者ctl<0就可以知道他是否是Running状态

在这里插入图片描述

execute方法

上面那个流程图应该大差不差了,这里就不细讲了

addWorker方法

retry机制

continue retry表示回到最外层循环体,然后继续向下执行,如下面代码在碰到continue retry后,直接执行i++,判定i<2,成功即进入 j 这个内层循环,否则就结束外层循环
break retry表示终止最外层循环,即直接结束这整个循环。
因为下面这个例子的retry就是放在最外层循环体之上的,所以continue和break都与最外层有关。实际上他是只与retry下面那一行的循环体有关。

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
//这里的retry可以换做其他单词,并不是固定死了,只是个标记而已,相当于C++的goto的loop
retry:
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 4; j++) {
if (j == 2) {
第一种:continue retry;
第二种:break retry;
}
}
}
}

第一部分:池内线程数加一

在这里插入图片描述

第二部分:创建新线程执行任务

上一环节内没有return false,就会来到这一环节

Woker类部分代码

把任务和执行他的线程封装到一起了
注意这个类继承了AQS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
/** 运行这个woker的线程。*/
final Thread thread;
/** 初始的要执行的方法、可能为null */
Runnable firstTask;

Worker(Runnable firstTask) {
setState(-1); // 直到runWoker()方法前,不让任务中断
this.firstTask = firstTask;
//这里可能导致thread为null
this.thread = getThreadFactory().newThread(this);
}

/** 将运行委托给runWoker()方法 */
public void run() {
runWorker(this);
}
}

Woker容器

1
2
/**包含池中所有Woker的集合。仅在持有 mainLock 时可以访问。 */
private final HashSet<Worker> workers = new HashSet<Worker>();

过程中用到的🔒

1
ReentrantLock mainLock

过程

在这里插入图片描述

runWorker()

他的主要处理逻辑就是执行传入的task或者通过getTask()方法从阻塞队列中获取的task,然后调用run方法进行执行。很有意思的是里面还夹杂了两个切面方法beforeExecute()和afterExecute(),他们定义为空方法体,可以按照自己的需求重写

getTask()

这个方法里终于用到传入的keepAliveTime和allowCoreThreadTimeOut。同时有一个标记timed决定这个线程是否会在一段时间后被回收。
主要处理逻辑就是:
从队列中获取任务,如果timed是true的话,则调用阻塞队列的poll方法阻塞一段时间获取任务,这段时间没任务的话,则超时设置timeOut=true,结束生命周期。否则调用take()方法一直阻塞等待任务到来,也就是核心线程为什么能一直存活的原因。

执行流程总图

在这里插入图片描述

  • 标题: java线程池学习
  • 作者: urlyy
  • 创建于 : 2022-07-15 12:50:17
  • 更新于 : 2023-06-19 13:16:39
  • 链接: https://urlyy.github.io/2022/07/15/java线程池学习/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。