我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码
一、架构
1.1 UML 图
1.2 Executors返回的线程池对象的弊端
- FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
- CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。
二、 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:抛弃当前任务
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永远没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。
2.3 线程池处理 UML 图
2.4 总结 ( 超级重点)
- 总共有3个地方可以放 task , core + queue + max
- max 池数量意味着同一时间有多少个线程可以并发执行,超过就排队去。
- 到达 core 和 max 的 task 都会立刻执行,但是max内的元素过期会被回收。
- 如果 woker 数量少于 corePoolSize,那么直接就执行 task ,如果是大于 corePoolSize,那么就暂时塞到 queue 里面去,等到 corePool 空闲下来,再从 queue拉取 task 来执行,如果queue没长度限制,那么可以一直塞到 queue里面去排队,如果 queue 满了,那么就直接让 core 去处理,假如此时超过 maximumPoolSize 的话,那么没办法,直接执行拒绝策略。
三、代码 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;
}
}
}
四、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 这个地方放东西,时间一到立马回收。
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的区别 ===
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一样。
五、源码剖析
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数量到达上限,那么就拒绝此线程
}
… 后续补充上!