本为为读书笔记,书籍为java并发编程的艺术(ThreadPool非jdk1.8)
本文源码采用jdk1.8
参考:https://www.cnblogs.com/leesf456/p/5585627.html
https://blog.csdn.net/programmer_at/article/details/79799267
文章目录
1.线程池基本概念
1.1 使用线程池的好处:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
1.2 线程池的实现原理
1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。
如果已经满了,则交给饱和策略(RejectedExecutionHandler)来处理这个任务。
1.3 处理流程(execute方法)
1.4 饱和(拒绝)策略
- AbortPolicy:直接抛出异常,默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
1.5 关闭线程池
- shutdown
将线程池里的线程状态设置成SHUTDOWN状态, 然后中断所有没有正在执行任务的线程. - shutdownNow
将线程池里的线程状态设置成STOP状态, 然后停止所有正在执行或暂停任务的线程.
2.手写线程池
- 需要一个阻塞队列
- 执行任务的工作线程
2.1 代码
class ThreadPool {
private BlockingQueue<Runnable> taskQueue;
private HashSet<Worker> workers = new HashSet<>();
//核心线程数
private int coreSize;
//获取任务的超时时间
private long timeout;
private TimeUnit timeUnit;
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.taskQueue = new BlockingQueue<>(queueCapacity);
}
public void execute(Runnable task) {
synchronized (workers) {
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
System.out.println("新增任务" + worker + task);
worker.start();
workers.add(worker);
} else {
System.out.println("新增任务进阻塞队列" + task);
taskQueue.put(task);
}
}
}
class Worker extends Thread {
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
public void run2() {
//执行任务 worker 结束 就说明阻塞队列里也没有任务了 就会一直停在task处,因为task是非超时阻塞
//就阻塞住了 ,如果使用poll则是非超时阻塞
while (task != null || (task = taskQueue.take()) != null) {
try {
System.out.println(Thread.currentThread().getName() + "执行任务" + task);
task.run();
} catch (Exception e) {
} finally {
task = null;
}
}
synchronized (workers) {
System.out.println(this + "worker被移除");
workers.remove(this);
}
}
public void run() {
//执行任务 worker 结束 就说明阻塞队列里也没有任务了 就会一直挺再take处,因为take是非超时阻塞
//就阻塞住了 ,如果使用poll则是非超时阻塞
while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
try {
System.out.println("poll");
System.out.println(Thread.currentThread().getName() + "执行任务" + task);
task.run();
} catch (Exception e) {
} finally {
task = null;
}
}
synchronized (workers) {
System.out.println(this + "worker被移除");
workers.remove(this);
}
}
}
}
class BlockingQueue<T> {
//任务队列
private Deque<T> queue = new ArrayDeque<>();
private ReentrantLock lock = new ReentrantLock();
//生产者条件变量
private Condition fullWaitSet = lock.newCondition();
//消费者条件变量
private Condition emptyWaitSet = lock.newCondition();
//容量
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
/**
* 带超时的阻塞获取
*
* @return
*/
public T poll(long timeout, TimeUnit timeUnit) {
lock.lock();
try {
long nanos = timeUnit.toNanos(timeout);
while (queue.isEmpty()) {
try {
if (nanos <= 0) {
return null;
}
nanos = emptyWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
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 element) {
lock.lock();
try {
while (queue.size() == capacity) {
try {
System.out.println("等待假如阻塞队列" + element);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(element);
emptyWaitSet.signal();
} finally {
lock.unlock();
}
}
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
2.2增加拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略
待定
所以我们把拒绝策略给抽象出来
思考为什么要传入原阻塞队列?
/**
* 使用策略模式
*/
interface rejectPolicy<T> {
void reject(BlockingQueue blockingQueue, T task);
}
然后增加拒绝策略成员变量:
private BlockingQueue.RejectPolicy<Runnable> rejectPolicy;
修改构造方法:
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity, BlockingQueue.RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.taskQueue = new BlockingQueue<>(queueCapacity);
this.rejectPolicy = rejectPolicy;
}
修改execute方法
public void execute(Runnable task) {
synchronized (workers) {
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
System.out.println("新增任务" + worker + task);
worker.start();
workers.add(worker);
} else {
System.out.println("新增任务进阻塞队列" + task);
// taskQueue.put(task);
taskQueue.tryPut(rejectPolicy, task);
}
}
}
/**
* 1.死等
* 2.带超时等待
* 3.放弃任务执行
* 4.抛出异常
* 5.让调用者自己执行任务
* @param rejectPolicy
* @param task
*/
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
//判断队列是否已满
if (queue.size() == capacity) {
rejectPolicy.reject(this, task);
} else {
//队列未满
queue.addLast(task);
emptyWaitSet.signal();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
其实原put方法就是一种死等策略
/**
* 这属于无限等待策略
* @param element
*/
public void put(T element) {
lock.lock();
try {
while (queue.size() == capacity) {
try {
System.out.println("阻塞队列已满" + element);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(element);
emptyWaitSet.signal();
} finally {
lock.unlock();
}
}
...
//死等策略
,(queue,task)->{
queue.put(task);
}
...
可重写阻塞队列里的响应方法来完成你想要的目的,例如超时等待等,这个时候queue就派上用场了
3.线程池源码(注意源码里面的注释)
手写 线程池后
看懂 ctl 就懂了一半,位操作可以自己手动弄几个例子试一下
主池控制状态ctl是一个原子整数包装两个概念领域
- workerCount,表示线程的有效数量
- runState,指示是否运行、关闭等
谨记高三位为状态位,而只有五种状态,是否就提供了扩展性呢?
除高三位外 其他为核心线程数
补一下位运算知识吧:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//线程池状态码
private static final int COUNT_BITS = Integer.SIZE - 3;
//0001 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
//可以看出状态码是-1 0 1 2 3 位移29位
//可以看出running的状态小于0 -1位移29位为负整数,每次添加一个线程,做++操作。
//1110 0000 0000 0000 0000 0000 0000 0000
//能接受新任务,队列中的任务可继续运行
private static final int RUNNING = -1 << COUNT_BITS;
//0000 0000 0000 0000 0000 0000 0000 0000
//不再接受新任务,队列中的任务仍可继续执行
private static final int SHUTDOWN = 0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000
//不再接受新任务,不再执行队列中的任务,中断所有执行中的任务(发中断消息)
private static final int STOP = 1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000
//所有任务均已终止,workerCount的值为0,转到TIDYING状态的线程即将要执行terminated()钩子方法.
private static final int TIDYING = 2 << COUNT_BITS;
//0110 0000 0000 0000 0000 0000 0000 0000
//terminated()方法执行结束
private static final int TERMINATED = 3 << COUNT_BITS;
//初始化线程数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//获取当前线程的运行状态 屏蔽高三位,只有低29位参与运算
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取当前的线程数 屏蔽高三位,只有低29位参与运算
private static int workerCountOf(int c) { return c & CAPACITY; }
//或操作
private static int ctlOf(int rs, int wc) { return rs | wc; }
我们先看5个状态,只看最高的3位,分别是:
RUNNING = 111
SHUTDOWN = 000
STOP = 001
TIDYING = 010
TERMINATED = 011
//大小关系:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
//所以存在这样的判断方法:
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
- RUNNING:正在处理任务和接受队列中的任务。
- SHUTDOWN:不再接受新的任务,但是会继续处理完队列中的任务。
- STOP:不再接受新任务,也不继续处理队列中的任务,并且会中止正在处理的任务。
- TIDYING:所有任务都已经处理结束,目前worker数为0,当线程池进入这个状态的时候,会调用terminated()方法。
- TERMINATED:线程池已经全部结束,并且terminated()方法执行完成。
其他重要参数:
//待执行线程队列
private final BlockingQueue<Runnable> workQueue;
//锁,基于重入锁,线程池核心之一
private final ReentrantLock mainLock = new ReentrantLock();
//线程队列,这是该线程池内已有线程
//注意与workQueue的区别
private final HashSet<Worker> workers = new HashSet<Worker>();
//多线程协调通信
private final Condition termination = mainLock.newCondition();
//拒绝handler,用于线程池不接受新加线程时的处理方式
//分为系统拒绝(线程池要关闭等),与线程池饱和(已达线程池最大容量)
private volatile RejectedExecutionHandler handler;
//线程工厂,新建线程池时带入
private volatile ThreadFactory threadFactory;
//默认拒绝向线程池中新加线程的方式:丢弃
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();
初始化参数详解:
int corePoolSize => 该线程池中核心线程数最大值
int maximumPoolSize 该线程池中线程总数最大值
long keepAliveTime 该线程池中非核心线程闲置超时时长
TimeUnit unit keepAliveTime的单位
BlockingQueue workQueue 该线程池中的任务队列:维护着等待执行的Runnable对象
ThreadFactory threadFactory 创建线程的方式,这是一个接口,你new他的时候需要实现他的Thread newThread(Runnable r)方法,
RejectedExecutionHandler handler 抛出异常专用的
3.1 execute()
注释翻译:
1.如果运行的线程小于corePoolSize,则尝试用给定的命令启动一个新线程任务。对addWorker的调用会自动地检查runState和 workerCount,从而通过返回false来防止在不应该添加线程的情况下添加线程的错误警报。
2.如果一个任务可以成功地进入队列,那么我们仍然需要来再次检查是否应该添加一个线程(因为现有的线程在最后一次检查后死亡),或者池在进入这个方法后关闭。因此,我们重新检查状态,如果有必要,如果停止,则回滚队列;如果没有,则启动一个新线程。
3.如果我们不能取出队列任务,然后我们尝试添加一个新的线程。如果它失败了,我们知道我们被关闭或饱和,所以拒绝任务。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 获取当前状态
int c = ctl.get();
//工作线程小于corePoolSize
if (workerCountOf(c) < corePoolSize) {
//添加一个core线程,此处参数为true,表示添加的线程是core容量下的线程
if (addWorker(command, true))
// 添加成功就直接返回
return;
// 添加线程不成功 刷新数据,乐观锁就是没有锁
c = ctl.get();
}
// 工作线程已满
//判断线程池运行状态,查看阻塞队列是否有空间
if (isRunning(c) && workQueue.offer(command)) {
//再次检测 状态
int recheck = ctl.get();
//如果 线程池不是运行状态 ,就移除任务 ,执行拒绝策略
if (! isRunning(recheck) && remove(command))
//执行拒绝策略
reject(command);
//否则 线程池在运行,但是 有效线程数为0
else if (workerCountOf(recheck) == 0)
//添加一个空线程进线程池,使用非core容量线程
//仅有一种情况,会走这步,core线程数为0,max线程数>0,队列容量>0
//创建一个非core容量的线程,线程池会将队列的command执行
addWorker(null, false);
}else if (!addWorker(command, false))
//线程池停止了或者队列已满
//添加maximumPoolSize容量工作线程
//如果失败,执行拒绝策略
reject(command);
}
3.2 addWorker()
说明:此函数可能会完成如下几件任务
① 原子性的增加workerCount。
② 将用户给定的任务封装成为一个worker,并将此worker添加进workers集合中。
③ 启动worker对应的线程,并启动该线程,运行worker的run方法。
④ 回滚worker的创建动作,即将worker从workers集合中删除,并原子性的减少workerCount。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//我比较喜欢叫线程里的死循环叫自旋 自旋添加 直到CAS成功或者添加失败
for (;;) { // 外层无限循环
// 获取线程池控制状态
int c = ctl.get();
// 获取状态
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && // 状态大于等于SHUTDOWN,初始的ctl为RUNNING,小于SHUTDOWN
! (rs == SHUTDOWN && // 状态为SHUTDOWN
firstTask == null && // 第一个任务为null
! workQueue.isEmpty())) // 阻塞队列不为空
// 以上 三个条件只要一个不满足 都返回 添加工作线程失败
return false;
// 死循环
for (;;) {
//得到 worker线程数量
int wc = workerCountOf(c);
if (wc >= CAPACITY || // worker数量大于等于最大容量
wc >= (core ? corePoolSize : maximumPoolSize)) // worker数量大于等于核心线程池大小或者最大线程池大小
// 返回添加工作线程失败
return false;
// 否则 尝试 CAS增加一个工作线程
if (compareAndIncrementWorkerCount(c)) // 比较并增加worker的数量 增加成功
// 跳出外层循环
break retry;
// 获取线程池控制状态
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs) // 此次的状态与上次获取的状态不相同
// 跳过剩余部分,继续循环
//直到 创建 CAS 成功 或者返回失败
continue retry;
// 否则CAS因workerCount更改而失败;重试内循环
}
}
// CAS 成功
// worker开始标识
boolean workerStarted = false;
// worker被添加标识
boolean workerAdded = false;
// 空指针
Worker w = null;
try {
// 初始化worker
w = new Worker(firstTask);
// 获取worker对应的线程
final Thread t = w.thread;
if (t != null) { // 线程不为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.
// 线程池的运行状态
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN || // 小于SHUTDOWN
(rs == SHUTDOWN && firstTask == null)) { // 等于SHUTDOWN并且firstTask为null
if (t.isAlive()) // precheck that t is startable // 线程刚添加进来,还未启动就存活 就说明有其他线程启动了t
// 抛出线程状态异常
throw new IllegalThreadStateException();
// 将worker添加到工作线程池
workers.add(w);
// 获取worker集合的大小
int s = workers.size();
if (s > largestPoolSize) // 队列大小大于largestPoolSize
// 重新设置largestPoolSize
largestPoolSize = s;
// 设置worker已被添加标识
workerAdded = true;
}
} finally {
// 释放锁
mainLock.unlock();
}
if (workerAdded) { // worker被添加
// 开始执行worker的run方法
t.start();
// 设置worker已开始标识
workerStarted = true;
}
}
} finally {
if (! workerStarted) // worker没有开始
// 添加worker失败
addWorkerFailed(w);
}
return workerStarted;
}
new woker:
Worker(Runnable firstTask) {
setState(-1); //禁止中断,直到运行worker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
3.3 worker.run()
接着上面,添加工作线程成功,并启动了 worker . thread.start 就是调用了
worker的run
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
工作线程:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行。我们可以从Worker类的run()方法里看到这点。
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 这个锁是允许打断的 解锁
boolean completedAbruptly = true;
try {
// 不断的从队列理 获取 task
while (task != null || (task = getTask()) != null) {
w.lock(); //锁住, 要执行任务了
// 如果池正在停止,确保线程被中断;
// 如果没有,请确保线程没有被中断。
// 要求在第二种情况下重新检查处理
// 现在关闭竞争,同时清除中断
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
// 至少是运行状态 或者 当前线程没有被打断
// 打断当前线程
wt.interrupt();
try {
//
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 执行任务
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//执行任务后
afterExecute(task, thrown);
}
} finally {
task = null;
// 完成任务数加一
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
说明:此函数中会实际执行给定任务(即调用用户重写的run方法),并且当给定任务完成后,会继续从阻塞队列中取任务,直到阻塞队列为空(即任务全部完成)。在执行给定任务时,会调用钩子函数,利用钩子函数可以完成用户自定义的一些逻辑。在runWorker中会调用到getTask函数和processWorkerExit钩子函数,其中,getTask函数源码如下
3.4 getTask()
说明:此函数用于从workerQueue阻塞队列中获取Runnable对象,由于是阻塞队列,所以支持有限时间等待(poll)和无限时间等待(take)。在该函数中还会响应shutDown和、shutDownNow函数的操作,若检测到线程池处于SHUTDOWN或STOP状态,则会返回null,而不再返回阻塞队列中的Runnalbe对象。
processWorkerExit函数是在worker退出时调用到的钩子函数,而引起worker退出的主要因素如下
① 阻塞队列已经为空,即没有任务可以运行了。
② 调用了shutDown或shutDownNow函数
private Runnable getTask() {
boolean timedOut = false; //上次poll超时了吗?
for (;;) { // 无限循环,确保操作成功
// 获取线程池控制状态
int c = ctl.get();
// 运行的状态
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 当前状态大于等于SHUTDOWN(表示调用了shutDown)并且(大于等于STOP(调用了shutDownNow)或者阻塞队列为空)
// 减少worker的数量
//表明运行状态下的 阻塞队列已经没有任务了 就减少
decrementWorkerCount();
// 返回null, 没有任务可被执行
return null;
}
// 获取worker数量
int wc = workerCountOf(c);
// worker会被淘汰吗?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 是否允许coreThread超时或者workerCount大于核心大小
if ((wc > maximumPoolSize || (timed && timedOut)) // worker数量大于maximumPoolSize
&& (wc > 1 || workQueue.isEmpty())) { // workerCount大于1或者worker阻塞队列为空(在阻塞队列不为空时,需要保证至少有一个wc)
if (compareAndDecrementWorkerCount(c)) // 比较并减少workerCount
// 返回null,不执行任务,该worker会退出
return null;
// 跳过剩余部分,继续循环
continue;
}
try {
//这里出现了 线程池的参数 ,等待获取任务超时时间
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // 等待指定时间
workQueue.take(); // 一直等待,直到有元素
if (r != null)
return r;
// 等待指定时间后,没有获取元素,则超时
timedOut = true;
} catch (InterruptedException retry) {
// 抛出了被中断异常,重试,没有超时
timedOut = false;
}
}
}
3.5 shutdown()
说明:此函数会按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务。首先会检查是否具有shutdown的权限,然后设置线程池的控制状态为SHUTDOWN,之后中断空闲的worker,最后尝试终止线程池。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 检查shutdown权限
checkShutdownAccess();
// 设置线程池控制状态为SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断空闲worker
interruptIdleWorkers();
// 调用shutdown钩子函数
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试终止
tryTerminate();
}
4. JDK 线程池的使用
4.1 参数介绍
介绍一下几个参数就行:
一、corePoolSize 线程池核心线程大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会 被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
二、maximumPoolSize 线程池最大线程数量
一个任务被提交到线程池后,首先会缓存到工作队列(后面会介绍)中,如果工作队列满了,则会创建一个新线程,然后从工作队列中的取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize来指定。
三、keepAliveTime 空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
四、unit 空间线程存活时间单位
keepAliveTime的计量单位
五、workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。
4.2合理地配置线程池
要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。
❑ 任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
❑ 任务的优先级:高、中和低。
❑ 任务的执行时间:长、中和短。
❑ 任务的依赖性:是否依赖其他系统资源,如数据库连接。
CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。
混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。
可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
4.3 线程池的监控
如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性。
-
taskCount:线程池需要执行的任务数量。
-
completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
-
largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
-
getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。❑ getActiveCount:获取活动的线程数。
建议使用有界队列。
4. Executor框架
Executor:是Java线程池的超级接口;提供一个execute(Runnable command)方法;我们一般用它的继承接口ExecutorService。
Executors:是java.util.concurrent包下的一个类,提供了若干个静态方法,用于生成不同类型的线程池。
ExecutorService:它是线程池定义的一个接口,继承Executor。
4.1 Executor框架的两级调度模型
Java线程(java.lang.Thread)被一对一映射为本地操作系统线程。
Java线程启动时会创建一个本地操作系统线程;
当该Java线程终止时,这个操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU。
4.2 Executor框架的结构
-
任务。包括被执行任务需要实现的接口:Runnable接口或Callable接口。
-
任务的执行。包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。
-
异步计算的结果。包括接口Future和实现Future接口的FutureTask类。
ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。
4.3 Executor框架的成员
ThreadPoolExecutor、ScheduledThreadPoolExecutor、Future接口、Runnable接口、Callable接口和Executors。
4.3.1 ThreadPoolExecutor
1)FixedThreadPool
创建一个线程池,重用固定数量的线程,从共享无界队列中运行,使用提供的ThreadFactory在需要时创建新线程。
在任何时候,最多nThreads个线程将处于主动处理任务。 如果所有线程处于活动状态时都会提交其他任务,则它们将等待队列中直到线程可用。 如果任何线程由于在关闭之前的执行期间发生故障而终止,则如果需要执行后续任务,则新线程将占用它。 池中的线程将存在,直到它明确地为shutdown 。
下面是Executors提供的,创建使用固定线程数的FixedThreadPool的API。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
它适用于负载比较重的服务器。
2) SingleThreadExecutor
创建一个使用单个工作线程运行无界队列的执行程序,并在需要时使用提供的ThreadFactory创建一个新线程。 与其他等效的newFixedThreadPool(1, threadFactory) newFixedThreadPool(1, threadFactory) ,返回的执行器保证不被重新配置以使用额外的线程。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
3) CachedThreadPool
创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。 这些池通常会提高执行许多短暂异步任务的程序的性能。 调用execute将重用以前构造的线程(如果可用)。 如果没有可用的线程,将创建一个新的线程并将其添加到该池中。 未使用六十秒的线程将被终止并从缓存中删除。 因此,长时间保持闲置的池将不会消耗任何资源。 请注意,可以使用ThreadPoolExecutor构造函数创建具有相似属性但不同详细信息的池(例如,超时参数)。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。
4.3.2 ScheduledThreadPoolExecutor
1) SingleThreadScheduledExecutor
创建一个单线程执行器,可以调度命令在给定的延迟之后运行,或定期执行。 (请注意,如果这个单个线程由于在关闭之前的执行过程中发生故障而终止,则如果需要执行后续任务,则新的线程将占用它。)任务保证顺序执行,并且不超过一个任务将被激活在任何给定的时间。 与其他等效的newScheduledThreadPool(1) newScheduledThreadPool(1) ,返回的执行器保证不被重新配置以使用额外的线程。
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
SingleThreadScheduledExecutor适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。
2) ScheduledThreadPoolExecutor
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
ScheduledThreadPoolExecutor适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。
4.3.3 Future接口
Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
<T> Future<T> submit(Callable<T> task);
4.3.4 Runnable接口和Callable接口
Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或Scheduled-ThreadPoolExecutor执行。它们之间的区别是Runnable不会返回结果,而Callable可以返回结果。
除了可以自己创建实现Callable接口的对象外,还可以使用工厂类Executors来把一个Runnable包装成一个Callable。
4.4 ThreadPoolExecutor详解
4.4.1 FixedThreadPool详解
FixedThreadPool被称为可重用固定线程数的线程池。
其他初始化参数不解释了;
keepalive参数解释:
当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。这里把keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止。
execute方法流程如下:
1)如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。2)在线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入LinkedBlockingQueue。
3)线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。
FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响。
1)当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。
2)由于1,使用无界队列时maximumPoolSize将是一个无效参数。
3)由于1和2,使用无界队列时keepAliveTime将是一个无效参数。
4)由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)。
4.4.2 SingleThreadExecutor详解
需要有一点阻塞队列的知识
SingleThreadExecutor是使用单个worker线程的Executor。
SingleThreadExecutor使用无界队列作为工作队列对线程池带来的影响与FixedThreadPool相同
1)如果当前运行的线程数少于corePoolSize(即线程池中无运行的线程),则创建一个新线程来执行任务。
2)在线程池完成预热之后(当前线程池中有一个运行的线程),将任务加入Linked-BlockingQueue。
3)线程执行完1中的任务后,会在一个无限循环中反复从LinkedBlockingQueue获取任务来执行。
4.4.3 CachedThreadPool详解
CachedThreadPool是一个会根据需要创建新线程的线程池。
CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为Integer.MAX_VALUE,即maximumPool是无界的。这里把keepAliveTime设置为60L,意味着CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。
CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的maximumPool是无界的。
极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
execute方法运行示意图:
4.5 ScheduledThreadPoolExecutor详解
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。
ScheduledThreadPoolExecutor的功能与Timer类似,
但ScheduledThreadPoolExecutor功能更强大、更灵活。Timer对应的是单个后台线程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。
线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。
ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下的修改。
- 使用DelayQueue作为任务队列。
- 获取任务的方式不同(后文会说明)。
- 执行周期任务后,增加了额外的处理(后文会说明)。
ScheduledThreadPoolExecutor的实现
ScheduledThreadPoolExecutor会把待调度的任务(ScheduledFutureTask)放到一个DelayQueue中。ScheduledFutureTask主要包含3个成员变量
如下
- long型成员变量time,表示这个任务将要被执行的具体时间。
- long型成员变量sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor中的序号。
- long型成员变量period,表示任务执行的间隔周期。
DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的Scheduled-FutureTask进行排序。排序时,time小的排在前面(时间早的任务将被先执行)。如果两个ScheduledFutureTask的time相同,就比较sequenceNumber,sequenceNumber小的排在前面(也就是说,如果两个任务的执行时间相同,那么先提交的任务将被先执行)。
DelayQueue. take()
DelayQueue. add()
todo
看这个源码要看吐了,就这样吧,有时间心情好了,再来仔细看Executors部分;