jdk1.8 ThreadPoolExecutor

ThreadPoolExecutor实际就是java的线程池,开发常用的Executors.newxxxxx()来创建不同类型和作用的线程池,其底部实际都是ThreadPoolExecutor。

1.创建 构造方法
以最多参数的为例

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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize:核心的线程数
maximumPoolSize:最大线程数
keepAliveTime:当现有线程数大于核心线程数时,没有执行任务的线程存活时间 CachedThreadPool弹性线程池有用60s,其它是0表示不回收
unit:存活时间单位
workQueue:阻塞的工作队列存放需要执行的任务
threadFactory:创建线程的工厂,实际就是给每个线程一个名,常见的pool-1-thread名就是它完成的
handler:线程池饱和策略,当工作队列满了,线程池创建的线程满了,就是忙不过来的时候线程池的处理方案 workQueue:阻塞队列实现主要有5种
1、ArrayBlockingQueue:数组结构的有界阻塞队列,按FIFO排序任务;
2、LinkedBlockingQuene:链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
固定线程池、单线程池
3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
弹性线程池
4、priorityBlockingQuene:具有优先级的无界阻塞队列;
5、DelayedWorkQueue:有定时性子的阻塞队列
定时线程池 handler:线程池饱和策略,主要有4种
1、AbortPolicy:直接抛出异常,默认策略;
2、CallerRunsPolicy:用调用者所在的线程来执行任务;
3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4、DiscardPolicy:直接丢弃任务;

2.线程池执行任务execute方法
常用的invokeAll、invokeAny、submit是上层ExecutorService又封装的执行策略,底层只有execute

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.
*
* 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.
*
* 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.
*/
int c = ctl.get();
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))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}

ctl:需要详细学习,顺便都看了
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//初始高3位1后面全0,从后面代码可以知道,ctl实际是用高3位表示线程池状态,后面29位表示当前线程数
private static final int COUNT_BITS = Integer.SIZE - 3;//32-3=29
private static final int CAPACITY = (1 << COUNT_BITS) - 1;

//明显这5个代表当前线程池状态,实际使用高3位来表示
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;//-1左移位29结果11100000000000000000000000000000 高3位是1 接受新任务并且处理已经进入阻塞队列的任务
private static final int SHUTDOWN = 0 << COUNT_BITS;//000 不接受新任务,但是处理已经进入阻塞队列的任务
private static final int STOP = 1 << COUNT_BITS;//001 不接受新任务,不处理已经进入阻塞队列的任务并且中断正在运行的任务
private static final int TIDYING = 2 << COUNT_BITS;//010 所有的任务都已经终止,workerCount为0, 线程转化为TIDYING状态并且调用terminated钩子函数
private static final int TERMINATED = 3 << COUNT_BITS;//011 terminated钩子函数已经运行完成

// 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; }//安位或

一句一句:
int c = ctl.get(); 通过上面知道c有两个作用,线程池状态,线程数

if (workerCountOf(c) < corePoolSize) { workerCountOf(c)就不详细看了,作用是根据低29位获取当前线程数,判断小于核心线程数corePoolSize

if (addWorker(command, true)) 说明是小于核心线程数的这里就创建新线程执行该任务,创建成功那就没后面事了

c = ctl.get(); 这里说明创建新线程失败了,线程池大多是并发场景,别的线程先一步创建就可能导致超过核心线程数,这里再次获取线程池 ctl,因为可以肯定是有变化的

if (isRunning(c) && workQueue.offer(command)) { 能到这说明线程池的线程数>=corePoolSize,或者创建新线程失败也是超过核心线程数的,不管哪种情况都表明任务就需要等待了,
判断线程池是否RUNNING执行状态,是继续把当前任务添加到工作队列中入队

int recheck = ctl.get(); 再次检查,前面的检查执行状态和入队后这里的线程状态并发下是很可能有改变的

if (! isRunning(recheck) && remove(command)) 这里注意外层if检查线程池是执行状态的且入队成功的,再次检查如果变更了不是执行中,说明线程池关了,已经不再执行任务了,
当前任务从工作队列移除,执行饱和策略reject

else if (workerCountOf(recheck) == 0) 这里说明还是执行中,特别注意从工作队列中移除是不会失败的因为之前是保证入队成功了的。检查当前线程数没有线程就创建一个线程,
注意只是创建线程不是直接执行该任务,后续会取队列执行

else if (!addWorker(command, false)) 说明线程池非执行状态或提交任务失败,则创建新线程执行任务,实际是尝试提高线程数到最大线程数,addWorker中会判断线程池状态和线程数的,
失败就饱和策略

3.addworker创建新线程执行任务

private boolean addWorker(Runnable firstTask, boolean core) {
retry: //标签
for (;;) { //死循环 jdk多线程常干的事,然后CAS
int c = ctl.get(); //获取线程状态
int rs = runStateOf(c);//获取高3位即线程池状态 // Check if queue empty only if necessary.
if (rs >= SHUTDOWN && //线程池非运行状态
! (rs == SHUTDOWN && // 状态为SHUTDOWN
firstTask == null && // 传递的任务为null,对于execute方法线程池满编入队之后 会在这里过滤掉执行后面创建新线程去执行队列
! workQueue.isEmpty())) // worker队列不为空 这里也说明SHUTDOWN还会继续处理队列中的任务
return false; for (;;) {
int wc = workerCountOf(c); //当前线程数量
if (wc >= CAPACITY || //线程数大于等于最大容量
wc >= (core ? corePoolSize : maximumPoolSize)) //线程数大于等于核心线程数或最大线程数 根据传值判断
return false; //直接结束
if (compareAndIncrementWorkerCount(c)) // CAS增加线程数的数量
break retry; //跳出外层循环
c = ctl.get(); // Re-read ctl CAS增加线程数数量失败,再次获取线程池状态
if (runStateOf(c) != rs) // 状态如果变更则说明需要重新获取状态,即跳出内层循环再次执行外循环,没变更继续执行内层循环
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//实际上面这部分就是CAS增加线程数,保证成功 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 {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {//检查线程池状态
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); //加锁的用意在这,保证并发下hashset插入正确
int s = workers.size();
if (s > largestPoolSize)//用于记录线程池中曾经有的最大的线程数
largestPoolSize = s;
workerAdded = true;//说明线程创建成功
}
} finally {
mainLock.unlock();//释放锁
}
if (workerAdded) {
t.start();//启动线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)//线程启动失败,创建是成功的
addWorkerFailed(w);//启动失败处理方案
}
return workerStarted;
}

4.核心线程Worker相关

private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
AQS模式 独占模式的AQS 实际也是线程池的线程调度实现方式 /**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker 设置AQS本节点状态是正常调度状态
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);//用给定的或默认的线程工厂创建新线程
} /** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);//执行线程start 实际是执行这
}

5.runWorker

final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts 这一步解锁,看AQS知道1初始化本节点状态0,2唤醒AQS的线程调度
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {//获取任务去执行
w.lock();//实际是把本线程放入AQS调度队列中等待调度
// 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;//标识是否是异常完成,上面是死循环,没任务了线程中断肯定是false,因异常中断的就是true
} finally {
processWorkerExit(w, completedAbruptly);//主要作用是没任务了,检查是否需要中断本线程
}
}

总结:
线程池实际就是线程安全的用hashset维护一个指定策略的线程集,用于执行提交的任务。其中使用AQS独占模式线程调度

ThreadPoolExecutor 线程池 简单解析的更多相关文章

  1. ThreadPoolExecutor 线程池的源码解析

    1.背景介绍 上一篇从整体上介绍了Executor接口,从上一篇我们知道了Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newSchedul ...

  2. Java并发——ThreadPoolExecutor线程池解析及Executor创建线程常见四种方式

    前言: 在刚学Java并发的时候基本上第一个demo都会写new Thread来创建线程.但是随着学的深入之后发现基本上都是使用线程池来直接获取线程.那么为什么会有这样的情况发生呢? new Thre ...

  3. 13.ThreadPoolExecutor线程池之submit方法

    jdk1.7.0_79  在上一篇<ThreadPoolExecutor线程池原理及其execute方法>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法 ...

  4. 手写线程池,对照学习ThreadPoolExecutor线程池实现原理!

    作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...

  5. 源码剖析ThreadPoolExecutor线程池及阻塞队列

    本文章对ThreadPoolExecutor线程池的底层源码进行分析,线程池如何起到了线程复用.又是如何进行维护我们的线程任务的呢?我们直接进入正题: 首先我们看一下ThreadPoolExecuto ...

  6. 手写一个线程池,带你学习ThreadPoolExecutor线程池实现原理

    摘要:从手写线程池开始,逐步的分析这些代码在Java的线程池中是如何实现的. 本文分享自华为云社区<手写线程池,对照学习ThreadPoolExecutor线程池实现原理!>,作者:小傅哥 ...

  7. j.u.c系列(01) ---初探ThreadPoolExecutor线程池

    写在前面 之前探索tomcat7启动的过程中,使用了线程池(ThreadPoolExecutor)的技术 public void createExecutor() { internalExecutor ...

  8. ThreadPoolExecutor 线程池

    TestThreadPoolExecutorMain package core.test.threadpool; import java.util.concurrent.ArrayBlockingQu ...

  9. 十、自定义ThreadPoolExecutor线程池

    自定义ThreadPoolExecutor线程池 自定义线程池需要遵循的规则 [1]线程池大小的设置 1.计算密集型: 顾名思义就是应用需要非常多的CPU计算资源,在多核CPU时代,我们要让每一个CP ...

随机推荐

  1. PHP读取文件和目录

    1:目录列表 2:文件列表

  2. VMware下扩展Ubuntu根分区大小

    一.查看磁盘信息 使用df命令用来查看文件系统的磁盘空间占用情况,由下可知我的/dev/sda1分区是挂载在根目录下的,本文目标就是来扩容/dev/sda1分区. 二.下载Gparted工具 Gpar ...

  3. 【Linux命令】id,usermod用户管理命令(包括/etc/passwd、shadow、group、gshadow文件)

    一.id命令 可以用来查看用户的UID.GID和附加组信息 id会显示用户以及所属群组的实际与有效ID.若两个ID相同,则仅显示实际ID.若仅指定用户名称,则显示目前用户的ID. 1.格式 id [O ...

  4. Oracle define用法简介教程

    目录 1.define常量用法 2.&和&&符号用法 继上一篇博客Oracle绑定变量学习笔记,再写一篇define变量的简单教程 @ 1.define常量用法 注意:defi ...

  5. Anaconda安装第三方库与pip和conda 添加国内源

    Anaconda安装第三方库 PIP使用命令 Anaconda命令 pip和conda 添加国内源 1:PIP相关命令 卸载 pip uninstall XXX 1.升级pip python -m p ...

  6. Nginx之前后端分离(入门)

    几个月前,公司架构优化,首先就是前后端分离. 所谓前后端分离,就是在传统的前后端代码都在一个项目里的基础上,将前后端代码抽离,把前端代码从后端项目了分离出来,前后端开发人员各自在自己的项目里开发. 为 ...

  7. LinkedTransferQueue

    /** *LinkedTransferQueue是有容量的, * 当第一个生产者线程调用transfer时,如果没有消费者,会阻塞. * 第二个生产者线程调用transfer时,如果没有消费者,会添加 ...

  8. 【踩坑系列】VS2019提示 ' the package could not be found in c\users\username\nuget\packages\. '

    解决步骤 1.删除对应项目下的 obj 文件夹 2.重新生成该项目

  9. asp.net core 新建area使用asp-action,asp-controller不管用

    解决方法: 在新建的Area目录下,这里使用Admin,Admin/Views下新建_ViewImports.cshtml和_ViewStart.cshtml两个视图文件,复制项目自动生成的到对应的新 ...

  10. Linux网络——配置网络之ifconfig家族命令

    Linux网络——配置网络之ifconfig家族命令 摘要:本文主要学习了ifconfig家族用来配置网络的命令. ifconfig命令 ifconfig命令用来显示或设置网络接口信息,设置只是临时生 ...