我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码
文章目录
- 一、架构
- 1.1 UML 图
- 1.2 Executors返回的线程池对象的弊端
- 二、 ThreadPoolExecutor 剖析
- 2.1 参数说明
- 2.2 线程池规则
- 2.3 线程池处理 UML 图
- 2.4 总结 ( 超级重点)
- 三、代码 Demo
- 四、Executors 的 4 个常见方法底层
- 4.1 Executors 的四个常用方法
- 4.2 newCachedThreadPool 底层
- 4.3 newFixedThreadPool 底层
- 4.4 newScheduledThreadPool 底层
- 4.5 newSingleThreadExecutor 方法
- 五、源码剖析
- 5.1 execute 方法
- 5.2 addWorker 方法
- 六、番外篇
一、架构
1.1 UML 图
1.2 Executors返回的线程池对象的弊端
- FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
- CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。
=== 点击查看top目录 ===
二、 ThreadPoolExecutor 剖析
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
2.1 参数说明
参数 | 描述 |
corePoolSize | 线程池核心线程数(平时保留的线程数) |
maximumPoolSize | 线程池最大线程数(线程池最多能起多少Worker)(当workQueue都放不下时,启动新线程,最大线程数) |
keepAliveTime | 超出corePoolSize数量的线程的保留时间。 |
unit | keepAliveTime单位 |
workQueue | 阻塞队列,存放来不及执行的线程 |
threadFactory | 线程工厂 |
handler | 饱和策略 |
- 关于 workQueue 阻塞队列:
- ArrayBlockingQueue:构造函数一定要传大小
- LinkedBlockingQueue:构造函数不传大小会默认为(Integer.MAX_VALUE ),当大量请求任务时,容易造成 内存耗尽。
- SynchronousQueue:同步队列,一个没有存储空间的阻塞队列 ,将任务同步交付给工作线程。
- PriorityBlockingQueue : 优先队列
- 关于 handler 饱和策略:
- AbortPolicy(默认):直接抛弃
- CallerRunsPolicy:用调用者的线程执行任务,直接执行
- DiscardOldestPolicy:抛弃队列中最久的任务
- DiscardPolicy:抛弃当前任务
=== 点击查看top目录 ===
2.2 线程池规则
- 线程池数量无限制:
- 如果Worker数量 <= corePoolSize,那么直接启动一个核心线程来执行 Task,不会放入队列中。
- 如果 corePoolSize < Worker数量 < maximumPoolSize:
a.并且任务队列是LinkedBlockingDeque的时候,超过 corePoolSize 的 Task 会放在任务队列中排队。
b.并且任务队列是SynchronousQueue的时候,线程池会创建新线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。- 如果 maximumPoolSize < Worker数量:
a.当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的 maximumPoolSize 设置是无效的,他的线程数最多不会超过 corePoolSize。
b.当任务队列是SynchronousQueue的时候,会因为线程池拒绝添加任务而抛出异常。
- 线程池数量有限制:
- 当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。
synchronousQueue永远没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。
=== 点击查看top目录 ===
2.3 线程池处理 UML 图
2.4 总结 ( 超级重点)
- 总共有3个地方可以放 task , core + queue + max
- core先执行、core满就去queue排队、queue队伍满就去max执行,max也满了的话,直接执行拒绝策略
- core干完就去消费queue
- max中的线程,空闲一段时间就结束
=== 点击查看top目录 ===
三、代码 Demo
- 情况:
/** ===== SynchronousQueue ==== **/
//1. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。
//2. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。
//3. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。max 满了,抛异常。/** ===== LinkedBlockingDeque ==== **/
// 1. 每次+3,塞满 core ,在 queue 里面排队, 由于 LinkedBlockingDeque 无限制数量,所以可以无限数量的 task 排队,然后core空闲了就去queue取task
// 2. 每次+3,塞满 core ,在 queue 里面排队,queue满了,直接丢 max 去执行,后期回收 max 的位置
// 3. 每次+3,塞满 core ,在 queue 里面排队, queue 满里,max 也满了 ,抛出异常
package indi.sword.util.concurrent;
import java.util.concurrent.*;
public class _17_01_TestThreadPool {
public static void main(String[] args) throws Exception {
/** ===== SynchronousQueue ==== **/
//1. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。
// testQueue(6, 8, new SynchronousQueue<>());
//2. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。
// testQueue(3, 6, new SynchronousQueue<>());
//3. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。max 满了,抛异常。
// testQueue(3, 5, new SynchronousQueue<>());// RejectedExecutionException 会导致整个 executor 停止
/** ===== LinkedBlockingDeque ==== **/
// 1. 每次+3,塞满 core ,在 queue 里面排队, 由于 LinkedBlockingDeque 无限制数量,所以可以无限数量的 task 排队,然后core空闲了就去queue取task
// testQueue(1, 3, new LinkedBlockingDeque<>());
// 2. 每次+3,塞满 core ,在 queue 里面排队,queue满了,直接丢 max 去执行,后期回收 max 的位置
// testQueue(1, 5, new LinkedBlockingDeque<>(1));
// 3. 每次+3,塞满 core ,在 queue 里面排队, queue 满里,max 也满了 ,抛出异常
testQueue(1, 3, new LinkedBlockingDeque<>(1));
}
/**
* 队列任务数永远是 0
*
* @author jeb_lin
* 5:32 PM 2019/10/24
*/
public static void testQueue(int corePoolSize,
int maximumPoolSize,
BlockingQueue<Runnable> workQueue) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 5, TimeUnit.SECONDS, workQueue);
try {
System.out.println("输入: corePoolSize -> " + corePoolSize + ", maximumPoolSize -> " + maximumPoolSize);
executor.execute(new MyRunnable(1));
executor.execute(new MyRunnable(2));
executor.execute(new MyRunnable(3));
System.out.println("---先开三个---");
print(executor);
executor.execute(new MyRunnable(4));
executor.execute(new MyRunnable(5));
executor.execute(new MyRunnable(6));
System.out.println("---再开三个---");
print(executor);
Thread.sleep(8000);
System.out.println("----8秒之后----");
print(executor);
} finally {
executor.shutdown();
}
}
private static void print(ThreadPoolExecutor executor) {
System.out.println("核心线程数 -> " + executor.getCorePoolSize()
+ ", 线程池数 -> " + executor.getPoolSize()
+ ", 队列任务数 -> " + executor.getQueue().size()
+ ", queue -> " + executor.getQueue().toString());
}
private static class MyRunnable implements Runnable {
private int index;
public MyRunnable(int index){
this.index = index;
}
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("thread-" + this.index + " run");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String toString(){
return "thread-" + index;
}
}
}
=== 点击查看top目录 ===
四、Executors 的 4 个常见方法底层
4.1 Executors 的四个常用方法
方法 | 描述 |
newCachedThreadPool | 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 (线程可复用) |
newFixedThreadPool | 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 |
newScheduledThreadPool | 创建一个定长线程池,支持定时及周期性任务执行。 |
newSingleThreadExecutor | 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 |
4.2 newCachedThreadPool 底层
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 (线程可复用)
- java.util.concurrent.Executors#newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- corePoolSize 竟然为0 ,也就意味着过期了就全部回收,不剩余
- maximumPoolSize 为默认的最大值,也就是支持无限制的线程并发,也就意味着可能发生OOM
- keepAliveTime 空闲线程的最大等待时间,60s后没有被复用立马销毁
- 使用 SynchronousQueue ,说明 queue 并不放东西
- corePoolSize = 0 加上 SynchronousQueue,说明 queue + core 都不放东西,那么也就意味着只有 max 这个地方放东西,时间一到立马回收。
=== 点击查看top目录 ===
4.3 newFixedThreadPool 底层
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- java.util.concurrent.Executors#newFixedThreadPool(int) 方法
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- corePoolSize = n,规定了 Core 池里面最多放 n 个线程
- maximumPoolSize = n ,规定了池子中最多就是 n 个线程 = corePoolSize,也就是全部都是核心线程,多了排队。
- keepAliveTime = 0,因为 corePoolSize = maximumPoolSize,也就意味着放满 core 满了,不会再放到 max 里面去,那么也意味着没什么好回收的了。
- LinkedBlockingQueue 链表内部结构,无定义长度,意味着线程可以不断进来,不断去排队。允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
- === 关于 LinkedBlockingQueue 与 ArrayBlockingQueue的区别 ===
=== 点击查看top目录 ===
4.4 newScheduledThreadPool 底层
创建一个定长线程池,支持定时及周期性任务执行。
- java.util.concurrent.ScheduledThreadPoolExecutor#ScheduledThreadPoolExecutor(int) 方法
- java.util.concurrent.Executors#newSingleThreadScheduledExecutor() 方法
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
- corePoolSize 自定义传入 N ,也就是core池最多n个线程,支持n线程同时并发
- maximumPoolSize 为 Integer.MAX_VALUE ,也就是支持无限制的线程并发,有OOM隐患,这个跟 newCachedThreadPool一样。
- keepAliveTime = 0,意味着,执行完毕立马回收
- 使用 DelayedWorkQueue ,意味着可以执行延迟操作。
4.5 newSingleThreadExecutor 方法
- java.util.concurrent.Executors#newSingleThreadExecutor() 方法
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- corePoolSize = maximumPoolSize = 1,意味着同一时刻只会有一个线程在执行。
- LinkedBlockingQueue 支持无限制的线程并发,有OOM隐患,这个跟 newCachedThreadPool一样。
=== 点击查看top目录 ===
五、源码剖析
5.1 execute 方法
- java.util.concurrent.ThreadPoolExecutor#execute 方法
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
* 如果当前的线程数小于核心线程池的大小,根据现有的线程作为第一个Worker运行的线程,
* 新建一个Worker,addWorker自动的检查当前线程池的状态和Worker的数量,
* 防止线程池在不能添加线程的状态下添加线程
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
* 如果线程入队成功,然后还是要进行double-check的,因为线程池在入队之后状态是可能会发生变化的
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*
* 如果task不能入队(队列满了),这时候尝试增加一个新线程,如果增加失败那么当前的线程池状态变化了或者线程池已经满了
* 然后拒绝task
*/
int c = ctl.get();
//当前的Worker的数量小于核心线程池大小时,新建一个Worker。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))//recheck防止线程池状态的突变,如果突变,那么将reject线程,防止workQueue中增加新线程
reject(command);
else if (workerCountOf(recheck) == 0)//上下两个操作都有addWorker的操作,但是如果在workQueue.offer的时候Worker变为0,
//那么将没有Worker执行新的task,所以增加一个Worker.
addWorker(null, false);
}
//如果workQueue满了,那么这时候可能还没到线程池的maxnum,所以尝试增加一个Worker
else if (!addWorker(command, false))
reject(command);//如果Worker数量到达上限,那么就拒绝此线程
}
5.2 addWorker 方法
private boolean addWorker(Runnable firstTask, boolean core) {
// 自旋,直到操作成功或者返回
retry:
for (;;) {
// 获取当前线程池的状态
int c = ctl.get();
// 获取线程池状态中的运行状态字段(WORKER_COUNT_BITS 部分)
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 当前线程池状态为 SHUTDOWN 或 STOPPED 时,禁止向线程队列添加任务
if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
return false;
for (;;) {
// 获取线程池状态中的 workerCount 字段(后面 29 位)
int wc = workerCountOf(c);
// 是否超过线程池最大容量
// core 为 true 表示 corePoolSize 已满,此时如果线程数到达 maximumPoolSize 仍旧在增加,那就不能再创建新的 worker 了
// core 为 false,表示 maximumPoolSize 已满,此时如果线程数到达 maximumPoolSize 仍旧在增加,那就不能再创建新的 worker 了
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 自增 workerCount 字段,如果成功就跳出当前循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 失败,重新获取线程池状态并重试
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 程序执行到这里,证明新建 Worker 线程应该是可以的,下面开始具体的操作
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 初始化 Worker 对象,并且传入 firstTask 作为第一个任务
w = new Worker(firstTask);
// 新建 Thread,Worker 线程就包装在这个 Thread 里面
final Thread t = w.thread;
if (t != null) {
// 加锁
final ReentrantLock mainLock = this.mainLock;
// 加锁成功标志
boolean workersAdded = false;
try {
// 将新建的线程添加到 HashSet<Worker> workers 中,然后再加到 HashSet<Thread> threads 中,也就是 t 线程加入线程池
mainLock.lock();
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
// 再次检查线程池终止状态是否被改变,若被改变则不再增加一个 Worker 线程
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 将 w 添加到 HashSet<Worker> workers 中
workers.add(w);
int s = workers.size();
// 将 workersAdded 标志设置为 true
workersAdded = true;
// 如果新建的 Worker 线程数量超过 maximumPoolSize,那么将最大线程数修改为实际数量
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
// 释放 mainLock 锁
mainLock.unlock();
}
// 如果 workersAdded 标志为 true,说明 Worker 线程已添加到线程池中,这里开始启动线程
if (workersAdded) {
// 真正启动 Worker 线程
t.start();
// workerStarted 标志设置为 true
workerStarted = true;
}
}
} finally {
// 确保 Worker 线程已启动成功
if (!workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
=== 点击查看top目录 ===
六、番外篇
上一章节:【线程】ThreadPool 线程池 Executors 实战 (十九)