ThreadPoolExecutor 类详解
一.线程池介绍
相信每个了解过Java线程的程序员都知道在高并发的应用程序中为了提高程序的效率,以及充分利用 机器CPU,大都会选择多线程来提高程序的执行效率(当然也并不是所有的场景使用多线程都会提高程序性能),但是我们也知道频繁的创建线程,销毁线程都会消耗时间,那么有没有一种方法可以避免这样频繁的创建和销毁线程呢;当然是有的,这就是我们本片文章的主角线程池,线程池的主要作用就是创建一个承载线程的容器,而使用这个容器去管理这些线程的生命周期, 当线程池里面的线程执行完任务时并不会马上销毁,而是可以继续执行其他任务,这样就大大提高了线程的执行效率,避免了频繁创建和销毁线程的消耗。
二.线程池的创建方式
Java中创建线程主要有两种方式
1.使用Exector类的静态方法创建
2.使用ThreadPoolExecutor创建
第一种方法将在后续文章中说明,今天主要学习ThreadPoolExecutor方式创建线程池
三.ThreadPoolExecutor创建线程池
3.1ThreadPoolExecutor线程池的构造方法
1.那么怎么使用ThreadPoolExecutor来创建线程池呢,查看ThreadPoolExecutor的源码可以发现ThreadPoolExecutor类里面给我们提供了以下4种构造方法,在这里只看地三种中就好了,其他三种其实内部都是调用第四种创建的线程池。
// 第一种构造器
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
// 第二种构造器
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
// 第三种构造器
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
// 第四种构造器
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
3.2 线程池构造方法的参数
了解了线程池的构造方法,那么接下来我们看构造方法的每个参数的意义,首先ThreadPoolExecutor的构造方法需要6个参数,而这6个参数也是ThreadPoolExecutor属性,下面我们看一个属性的意义
//corePoolSize 核心线程数,
private volatile int corePoolSize;
//线程池的最大创建线程数量
private volatile int maximumPoolSize;
//线程池执行任务的队列 一般来说有以下几重选择,此处不多解释,根据业务来选择(但是要注意内存溢出问题)
// ArrayBlockingQueue;
// LinkedBlockingQueue;
//SynchronousQueue;
private final BlockingQueue<Runnable> workQueue;
//线程在执行完任务后存活的时间,默认情况下是当maximumPoolSize>corePoolSize时起作用,也就是当前线程池存活的线程数大于corePoolSize时起作用,但是在创建线程池时如果调用threadPoolExecutor.allowCoreThreadTimeOut(boolean);方法时可控制是否让核心线程也销毁
private volatile long keepAliveTime;
//keepAliveTime的单位
TimeUnit unit
// 用于生产执行任务的线程工厂
private volatile ThreadFactory threadFactory;
// 队列存满后的拒绝策略 RejectedExecutionHandler共有4中实现所以相当于提供了4中拒绝策略
// 1.AbortPolicy 如果队列存满,直接抛异常(不推荐使用,但这是默认的拒绝策略,所以在使用线程池时最好是加上此参数)
// 2.CallerRunsPolicy 如果队列存满,则用主线程执行任务,直到程序关闭,最后丢弃任务
// 3.DiscardOldestPolicy 如果队列存满,则丢掉最旧未处理的任务,并且重试新任务,直到程序关闭,最后丢弃任务
// 4.DiscardPolicy 如果队列存满,则直接丢弃该异常任务
private volatile RejectedExecutionHandler handler;
3.3 execute方法详解
首先说明一下execute和submit方法的区别,execute方法是无返回值的,submit是有返回值的,submit方法返回值的原理是根据Future框架实现的,最终执行任务其实也是利用了execute方法,所以这里不过多的解释submit方法,至于 Future框架感兴趣的可自行了解以下
3.3.1再看execute方法前先了解一下几个参数
在看execute前先了解以下几个参数的意义
//AtomicInteger integer的原子操作类在这里的作用是高3位用于维护线程池运行状态,低29位维护线程池中线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 线程池的状态
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池的线程数量
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
// 线程池的状态
private static final int RUNNING = -1 << COUNT_BITS;//此状态可接收新任务,并且会处理已经在队列中的任务
private static final int SHUTDOWN = 0 << COUNT_BITS;//不可接收新任务,但是会处理在队列中的任务
private static final int STOP = 1 << COUNT_BITS;//不可接收新任务,不会处理在队列中的任务
private static final int TIDYING = 2 << COUNT_BITS;//不可接收新任务,所有任务都被终止
private static final int TERMINATED = 3 << COUNT_BITS;//不可接收新任务,
private static int runStateOf(int c) { return c & ~COUNT_MASK; }// 获取高3位保存的线程状态
private static int workerCountOf(int c) { return c & COUNT_MASK; }//获取低29位的线程数量
private static int ctlOf(int rs, int wc) { return rs | wc; }//合并线程状态和线程数量
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
private final HashSet<Worker> workers = new HashSet<>();//存放任务
3.3.2下面看execute方法
public void execute(Runnable command) {
// 空任务提交过来直接抛异常
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//判断当前线程池的数量与核心线程池数量的大小,如果小于则直接创建新的线程
if (workerCountOf(c) < corePoolSize) {
//创建新的线程,下面在说addWorker方法
if (addWorker(command, true))
return;
c = ctl.get();
}
// 当前线程池线程数量>核心线程数 isRuning判断的是当前线程的池的状态是否小于SHUTDOWN,
如果是将任务添加到队列(workQueue.offer此方法是添加任务到队列,此处不一定会加入成功,如
果队列里面任务大于创建队列的大小则加入失败)
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//二次检查如果当前线程的池的状态>SHUTDOWN,并且从队列里移除任务
if (! isRunning(recheck) && remove(command))
//满足上述条件执行决绝策略
reject(command);
else if (workerCountOf(recheck) == 0)
//不满足则加入队列
addWorker(null, false);
}
//重新尝试加入队列
else if (!addWorker(command, false))
//加入失败则执行拒绝策略
reject(command);
}
//继续看addWorker方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
for (;;) {
// 判断当前线程池线程数量事是否超过corePoolSize 或者maximumPoolSize数量
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
//利用cas给clt变量加1,成功则调到外部循环,失败则下次循环继续重试(不是很理解为什么要这么做)
if (compareAndIncrementWorkerCount(c))
break retry;
//重新获取clt的值
c = ctl.get(); // Re-read ctl
//查看线程池当前状态和SHUTDOWN的关系
if (runStateAtLeast(c, SHUTDOWN))
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;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 添加任务到workers,
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
//记录线程池中曾经出现过的最大线程数
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//执行任务,其实是执行Worker类中的run方法
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
3.2.3 Worker类
上面说到线程执行任务,那么线程是在哪执行的呢,答案就是执行Worker的run方法,下面就继续看Worker类的run方法
public void run() {
runWorker(this);
}
//直接看这个方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//获取firstTask
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//如果第一个task为空则根据getTask从队列中获取任务
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);
try {
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//退出线程
processWorkerExit(w, completedAbruptly);
}
}
//getTask方法作用是从队列中取任务
private Runnable getTask() {
//表示上次从阻塞队列中取任务时是否超时
boolean timedOut = false;
for (;;) {
int c = ctl.get();
//校验线程池状态,线程池状态大于SHUTDOWN时不允许添加任务
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//allowCoreThreadTimeOut ,默认false核心线程不会被销毁
// wc > corePoolSize;判断当前线程数量与核心线程数量大小
//timed 变量目的是判断是否允许超时回收
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// wc > maximumPoolSize可能是在此方法执行时执行了setMaximumPoolSize
//(timed && timedOut)说明需要超时回收
//(wc > 1 || workQueue.isEmpty()) 如果wc =1说明当前线程是线程池的最后一个线程了
//总结就是,当前线程数大于maximumPoolSize ,或者需要超时回收并且上次获取任务超时,同时当前线程数量大于1,
或者队列为空,当满足上述条件时说明线程池中已经不需要这么多线程了所以需要把多余的线程销毁所以调用
compareAndDecrementWorkerCount(c)给clt-1,如果失败则下次重试
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
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;
}
}
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//如果completedAbruptly==true说明出现异常需要给workCount-1,如果completedAbruptly==false说明在线程执行过程中没有异常那么在getTask方法中已经减过了
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//线程池执行完成的任务计数
completedTaskCount += w.completedTasks;
//任务已经完成,需要从workers移除
workers.remove(w);
} finally {
mainLock.unlock();
}
//确定是否是否应该关闭线程池
tryTerminate();
int c = ctl.get();
//如果线程池状态是running或者是shutdown并且是异常结束的那么直接给worker添加任务
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
//如果是正常结束的话判断allowCoreThreadTimeOut 是否允许回收核心线程
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//如果允许回收核心线程并且队列不为空的话,则需要保留最少一个线程去执行剩余任务
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//如果当前线程池剩余线程数量大于等于min的话,则直接返回
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
3.4 ThreadPoolExecutor类中其他常用方法
//关闭线程池,但是会执行之前提交的任务,不会接受新任务
public void shutdown() {
//略
}
//尝试停止所有正在执行的任务,停止正在等待的任务的处理,并返回正在等待执行的任务的列表。从该方法返回时,这些任务将从任务队列中排(删除)。
//此方法不等待主动执行的任务终止。使用{@link #awaitTermination awaitTermination}等待终止。
//除了尽最大努力尝试停止处理活动执行的任务之外,没有任何保证。此实现通过@link thread interrupt取消任务,因此任何未能响应中断的任务都可能永远不会终止。
//我理解就是立即终止任务所有任务,但是并不一定能终止成功,但是会返回未执行的任务
public List<Runnable> shutdownNow() {
//略
}
//判断线程池是否是runing状态
public boolean isShutdown() {
//略
}
//判断线程池是否是STOP或者TIDYING状态
public boolean isTerminating() {
//略
}
//判断线程是否是TERMINATED状态
public boolean isTerminated() {
//略
}
//启动核心线程,使其空闲地等待工作。这将覆盖仅在执行新任务时启动核心线程的默认策略。如果所有核心线程都已启动,则此方法将返回false
//解释一下就是启动核心线程,如果不执行此方法就是只有有新任务时才启动核心线程,所以相当于提前启动了核心线程
public boolean prestartCoreThread() {
//略
}
//与pretartcorethread相同,只是安排至少启动一个线程,即使corepoolsize为0。
void ensurePrestart() {
//略
}
//启动所有核心线程,使它们空闲地等待工作。这将覆盖仅在执行新任务时启动核心线程的默认策略。
public int prestartAllCoreThreads() {
//略
}
//从线程池队列中取消任务
public boolean remove(Runnable task) {
//略
}
//设置是否可以回收核心线程
//value=true 是可以回收核心线程,value=false 不可以回收核心线程,默认false
public void allowCoreThreadTimeOut(boolean value) {
//略
}
//删除工作队列中删除所有已经取消的任务(被取消的任务被其他线程操作时是无法删除的)
public void purge() {
//略
}
//返回当前正在执行任务的线程数(截止调用此方法时)
public int getActiveCount() {
//略
}
//返回线程池中存在过的最大线程数量
public int getLargestPoolSize() {
//略
}
//返回已计划执行的任务总数,已完成数量+正在执行的数量+队列中排队的数量(注意此数量不是准确的是大致的,原因是任务和线程的状态在计算过程中可能会动态更改)
public long getTaskCount() {
//略
}
//返回已完成的任务的总数(注意此数量不是准确的是大致的,原因是任务和线程的状态在计算过程中可能会动态更改,我理解的是返回的数量是在调用该方法那一刻之前完成的任务数量)
public long getCompletedTaskCount() {
//略
}
四 结束
到这里ThreadPoolExecutor的内容大概也学的差不多了,希望对喜欢编程的朋友有点作用,存在不足之处可以在评论区留言,一起探讨
来源:https://blog.csdn.net/qq1984705234/article/details/100110047