一.线程池简介
1.线程池的优点
- 线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。
- 可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。
二.自定义线程池
为什么要自定义线程池:因为JDK提供的线程池 将参数写死了,也许不太符合业务特点
1.线程池由哪些部件组成:线程集合、阻塞任务队列、拒绝策略
2.自定义阻塞任务队列:任务队列、锁、生产者条件变量、消费者条件变量、容量。
class BlockingQueue<T> {
// 1. 任务队列
private Deque<T> queue = new ArrayDeque<>();
// 2. 锁
private ReentrantLock lock = new ReentrantLock();
// 3. 生产者条件变量
private Condition fullWaitSet = lock.newCondition();
// 4. 消费者条件变量
private Condition emptyWaitSet = lock.newCondition();
// 5. 容量
private int capcity;
public BlockingQueue(int capcity) {
this.capcity = capcity;
}
// 阻塞获取
public T take() {
lock.lock();
try {
while (queue.isEmpty()) {
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
// 阻塞添加
public void put(T task) {
lock.lock();
try {
while (queue.size() == capcity) {
try {
log.debug("等待加入任务队列 {} ...", task);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}", task);
queue.addLast(task);
emptyWaitSet.signal();
} finally {
lock.unlock();
}
}
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
// 判断队列是否满
if(queue.size() == capcity) {
rejectPolicy.reject(this, task);
} else { // 有空闲
log.debug("加入任务队列 {}", task);
queue.addLast(task);
emptyWaitSet.signal();
}
} finally {
lock.unlock();
}
}
}
3.自定义线程池:阻塞任务队列、线程集合、核心线程数、
class ThreadPool {
// 任务队列
private BlockingQueue<Runnable> taskQueue;
// 线程集合
private HashSet<Worker> workers = new HashSet<>();
// 核心线程数
private int coreSize;
// 获取任务时的超时时间
private long timeout;
private TimeUnit timeUnit;
private RejectPolicy<Runnable> rejectPolicy;
// 执行任务
public void execute(Runnable task) {
// 当任务数没有超过 coreSize 时,直接交给 worker 对象执行
// 如果任务数超过 coreSize 时,加入任务队列暂存
synchronized (workers) {
if(workers.size() < coreSize) {
Worker worker = new Worker(task);
log.debug("新增 worker{}, {}", worker, task);
workers.add(worker);
worker.start();
} else {
// taskQueue.put(task);
// 1) 死等
// 2) 带超时等待
// 3) 让调用者放弃任务执行
// 4) 让调用者抛出异常
// 5) 让调用者自己执行任务
taskQueue.tryPut(rejectPolicy, task);
}
}
}
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity,
RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.taskQueue = new BlockingQueue<>(queueCapcity);
this.rejectPolicy = rejectPolicy;
}
class Worker extends Thread{
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
// 执行任务
// 1) 当 task 不为空,执行任务
// 2) 当 task 执行完毕,再接着从任务队列获取任务并执行
// while(task != null || (task = taskQueue.take()) != null) {
while(task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
try {
log.debug("正在执行...{}", task);
2. ThreadPoolExecutor
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
synchronized (workers) {
log.debug("worker 被移除{}", this);
workers.remove(this);
}
}
}
}
三.ThreadPoolExecutor
1.ThreadPoolExecutor应用方式
- 只需要构建好ThreadPoolExecutor对象即可,传入指定参数。
- 在执行Runnable任务时,可以直接调用execute方法。(ThreadPoolExecutor.execute(task))
- 在执行Callable任务时,需要有返回结果,直接调用submit方法。
2.核心参数
1. corePoolSize:指定了线程池中的核心线程数量。2. maximumPoolSize:指定了线程池中的最大线程数量。 非核心线程不是永久存在3. keepAliveTime:当前线程池数量超过 corePoolSize 时,多余的空闲线程的存活时间,即多次时间内会被销毁。4. unit:keepAliveTime 的单位。5. workQueue:任务队列,被提交但尚未被执行的任务。6. threadFactory:线程工厂,用于创建线程,一般用默认的即可。线程池初始默认为空7. handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。
3.执行流程分析
当业务线程(主线程)提交任务到线程池之后,任务的处理流程。
ThreadPoolExecutor 饱和策略定义 :如果当前同时运⾏的线程数量达到最⼤线程数量并且队列也已经被放满了任时, ThreadPoolTaskExecutor 定义⼀些策略 :
- ThreadPoolExecutor.AbortPolicy :抛出 RejectedExecutionException 来拒绝新任务的处理。
- ThreadPoolExecutor.CallerRunsPolicy :调⽤执⾏⾃⼰的线程运⾏任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应⽤程序可以承受此延迟并且你不能任务丢弃任何⼀个任务请求的话,你可以选择这个策略。
- ThreadPoolExecutor.DiscardPolicy : 不处理新任务,直接丢弃掉。
- ThreadPoolExecutor.DiscardOldestPolicy : 此策略将丢弃最早的未处理的任务请求。
4.线程池状态
(1)核心属性:CTL(原子类型)
CTL是什么:本质就是一个32位的int类型的数值;表示线程池状态和线程数量,这样就可以用一次 cas 原子操作进行赋值。
- 高3位表示了线程池当前的状态
- 低29位表示当前工作线程(核心+非核心)的数量
(2)状态变化
(3)execute()方法
public void execute(Runnable command) {
//非空判断
if (command == null)
throw new NullPointerException();
// 获取控制标识
int c = ctl.get();
//workerCountOf()拿到工作线程个数 是否小于核心线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
// 核心线程创建成功,则直接返回
return;
//在并发情况下 添加核心线程失败,更新CTL
c = ctl.get();
}
//创建核心线程失败的情况
//判断当前线程池状态是否为running
//若为running 将任务添加到 工作队列
if (isRunning(c) && workQueue.offer(command)) {
// 任务被成功添加到队列后,再获取一下控制标识
int recheck = ctl.get();
// 再check一下线程池状态,如果不是运行状态了,只有running状态 才接受任务
if (! isRunning(recheck) && remove(command))
// 如果成功从任务队列里删除了任务,那么还要调用reject方法来回调一个拒绝任务的处理策略
reject(command);
//工作线程是否为0
else if (workerCountOf(recheck) == 0)
//工作线程数为0,但队列中有任务
//添加一个空任务非核心线程 为了处理队列中剩余任务
addWorker(null, false);
}
//队列满了,尝试创建非核心线程
else if (!addWorker(command, false))
//创建失败 拒绝策略
reject(command);
}
(4)addWorker()方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry: // goto作用的标号,下面一定要接一个循环
for (;;) { // 这是个死循环,只能依赖内部的逻辑跳出,这种用法在concurrent的源码里很常见,主要都是为了循环检测状态
int c = ctl.get(); // 获取控制标识
int rs = runStateOf(c); // 获取当前线程池的状态
/*
如果 rs >= SHUTDOWN说明,线程池状态可能为SHUTDOWN、STOP、TIDYING、TERMINATED,
无论哪个状态,线程池都是拒绝再接收新任务的。在这样的前提下:
rs == SHUTDOWN : 说明正在关闭 ,还没有到终止状态
firstTask == null:说明并没有提交任务,只是为了增加工作线程
! workQueue.isEmpty():队列不为空说明还有任务未消费,可以通过增加工作线程协助消费
同时满足以上三个条件,也是可以继续往下执行新增工作线程的逻辑的,但是如果没有同时满足以上三个条件则 直接返回false
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 走到这一步起码说明,线程池还是可以继续处理任务的
for (;;) { // 又一个循环检测
int wc = workerCountOf(c); // 获取worker(工作线程)数目
/*
如果 工作线程数 已经大于CAPACITY(2的29次方减1) ,这个值很大了,很少有计算机能够支持这么多的线程数,如果大于这个值就直接返回false
如果core为true,说明调用方的目的是想创建核心工作线程,此时就要检测当前的工作线程数是否小于设置的核心线程数,如果大于这个值就直接返回false
如果core为false,说明调用方的目的就是想单纯的新增工作线程,此时就要检测当前工作线程数是否小于设置的最大线程数,如果大于这个值就直接返回false
*/
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 走到这一步,说明可以创建工作线程了,那么先把工作线程数递增(加1)
if (compareAndIncrementWorkerCount(c))
break retry; // 如果工作线程数递增成功,则通过break retry 可以跳出最外层的for循环
// 能够走到这一步,说明工作线程数递增失败,可能是由于其他线程的并发调用更改了工作线程数 或者 线程池状态发生了变更
c = ctl.get(); // Re-read ctl 重新获取一下控制标识 (在以上逻辑执行过程中,其他并发调用,可能会引起线程池状态变更)
// 重新获取线程池状态 ,如果和之前获取的不一致,那么需要跳转到retry标号,重新执行一次外层循环的逻辑,这样就可以重新获取一次线程池状态
if (runStateOf(c) != rs)
continue retry;
// 如果不是因为线程池状态问题导致的工作线程数变更失败,那么就继续执行内部循环逻辑(自旋重试)
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false; // 工作线程是否启动
boolean workerAdded = false; // 工作线程是否添加
Worker w = null; // 声明一个工作线程对象
try {
w = new Worker(firstTask); // 创建一个工作线程对象
final Thread t = w.thread; // 获取工作线程对象内部用于执行任务的thread对象
/*
t什么时候为空?
在执行Worker的构造函数时,就会实例化其内部的thread属性,实例化的方式,就是调用线程工厂的newThread方法。
而线程工厂是个接口,是可以用户自定义实现类的(也有默认的实现),用户实现的newThread方法是有可能由于编码失误返回null的。
*/
if (t != null) {
final ReentrantLock mainLock = this.mainLock; // 获取当前线程池的显示锁
mainLock.lock(); // 上锁
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
// 在持有锁的情况下重新check线程池状态,防止在创建worker阶段或者获取锁之前的逻辑执行时,状态发生变化。
int rs = runStateOf(ctl.get());
/*
rs < SHUTDOWN:说明是RUNNING状态,那么线程池是可以正常接收新提交的任务的
rs == SHUTDOWN && firstTask == null : 说明处于关闭状态,但是没有提交任务,这种情况线程池还是可以正常处理队列中现存的任务的。
*/
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 检查一下worker对象中的线程对象是否已经执行了start方法
// 如果已经执行了,说明状态不对,抛出线程状态异常。
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); // 将worker对象w添加到工作线程集合
int s = workers.size(); // 获取工作线程集合大小
// 这个largestPoolSize是统计线程的生命周期内曾经达到过的工作线程数的最大值
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true; // 工作线程是否添加的标识置为true
}
} finally {
mainLock.unlock(); // 释放锁
}
if (workerAdded) { // 如果成功的添加了工作线程
/*
启动工作线程
这个方法最终会执行Worker对象的run方法,内部又会调用runWorker方法。达到的效果就是,先处理firstTask,处理完之后再去队列中获取其他任务。
*/
t.start();
workerStarted = true; // 工作线程是否启动的标识置为true
}
}
} finally {
/*
在finally里块中检测工作线程是否启动标识
如果workerStarted为false (工作线程创建失败 或者 工作线程启动失败)
需要调用addWorkerFailed方法进行一些回滚操作
工作线程列表移除掉启动失败的工作线程
工作线程计数递减
*/
if (! workerStarted)
addWorkerFailed(w);
}
// 最后返回工作线程是否启动的标识
return workerStarted;
}