本为为读书笔记,书籍为java并发编程的艺术(ThreadPool非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)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
1.3 处理流程(execute方法)
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时,如果还有任务到来就会采取任务拒绝策略
待定
3.线程池源码(注意源码里面的注释)
主池控制状态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;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
//初始化线程数
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();
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);
}
//线程池停止了或者队列已满,添加maximumPoolSize容量工作线程,如果失败,执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
3.2 addWorker()
说明:此函数可能会完成如下几件任务
① 原子性的增加workerCount。
② 将用户给定的任务封装成为一个worker,并将此worker添加进workers集合中。
③ 启动worker对应的线程,并启动该线程,运行worker的run方法。
④ 回滚worker的创建动作,即将worker从workers集合中删除,并原子性的减少workerCount。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//我比较喜欢叫线程里的死循环叫自旋
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())) // worker队列不为空
// 返回
return false;
for (;;) {
// worker数量
int wc = workerCountOf(c);
if (wc >= CAPACITY || // worker数量大于等于最大容量
wc >= (core ? corePoolSize : maximumPoolSize)) // worker数量大于等于核心线程池大小或者最大线程池大小
return false;
if (compareAndIncrementWorkerCount(c)) // 比较并增加worker的数量
// 跳出外层循环
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;
// 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 // 线程刚添加进来,还未启动就存活
// 抛出线程状态异常
throw new IllegalThreadStateException();
// 将worker添加到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;
}
3.3 worker.run()
工作线程:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行。我们可以从Worker类的run()方法里看到这点。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
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; // Did the last poll() time out?
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阻塞队列为空)
// 减少worker的数量
decrementWorkerCount();
// 返回null,不执行任务
return null;
}
// 获取worker数量
int wc = workerCountOf(c);
// Are workers subject to culling?
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 待定
4. JDK 线程池的使用
4.1 参数介绍
介绍一下几个参数就行:
4.2合理地配置线程池
要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。
❑ 任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
❑ 任务的优先级:高、中和低。
❑ 任务的执行时间:长、中和短。
❑ 任务的依赖性:是否依赖其他系统资源,如数据库连接。
CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。
混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。
可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
4.3 线程池的监控
如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性。
❑ taskCount:线程池需要执行的任务数量。
❑ completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
❑ largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
❑ getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。❑ getActiveCount:获取活动的线程数。
建议使用有界队列。