04-JUC-ThreadPoolExecutor
随便说点什么吧
内容写的不多,后面的文章再补充,哈哈哈,开始
介绍
线程池有两个作用:
- 在大量异步任务时,可以提高性能
在执行大量的异步任务时,如果不使用线程池,每当执行异步任务时需要直接new出一个线程来运行,并且运行后需要进行销毁。在线程池的线程是可以复用的,不需要每次执行任务时需要进行创建和小安徽线程
- 合理的管理资源
线程池可以限制线程的个数,还可以根据情况动态的新增线程
源码分析
自从jdk1.5之后,线程执行和工作单元就被剥离开来,工作单元比如Callable和Runnable,而执行机制由Executors框架提供,下面是一个类图
下面是线程池的基本工作流程图:
流程图可以先放放, 先看看ThreadPoolExecutor几个重要的参数:下面是部分源码
public class ThreadPoolExecutor extends AbstractExecutorService { private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits 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; // 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; } ...//省略 }
- ctl:是一个原子变量类型,是一个控制状态的变量,但是它封装了两个概念:
- workerCount:工作线程数,表示有效线程数
- runState:表示运行状态,是否运行 是否关闭等
其中,从源码可以得知,workerCount被限制在2^29-1个线程数,而不是2^31-1,如果用32位的2进制表示的话,runState是位于高3位,workerCount位于低29位
运行状态有如下几种:
- RUNNING:Accept new tasks and process queued tasks,接收新的任务,并且处理在队列中的任务
- SHUTDOWN:Don't accept new tasks, but process queued tasks,不接收新的任务,但是处理在队列中任务
- STOP:Don't accept new tasks, don't process queued tasks, and interrupt in-progress tasks,不接收新任务,也不处理在队列中的任务,并且中断在执行中的任务
- TIDYING:All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method,所有的任务都已经终止,工作线程数为0,转换为TiDYing状态的下城将会执行teminated方法
- TERMINATED:terminated() has completed,teminated方法已经执行完成
- corePoolSize and maximumPoolSize:核心线程数和最大线程数
- keepAliveTime:存活时间,如果当前线程池中线程数量大于核心线程数量比较多,并且是闲置状态,则这些闲置的线程能存活的最大时间
- ThreadFactory:创建线程的工程
- workQueue:阻塞队列,用来保存正在等待执行的任务,这个队列的使用与池子的大小相互作用
- 如果运行的线程少于核心线程数,则Executor框架偏向于新建一个线程执行,而不是放到队列中
- 如果达到了核心线程数,则Executor框架偏向于把任务添加到阻塞队列中,而不是创建一个请求线程
- 如果请求不能排队了,也就是阻塞队列的任务已经满了,并且创建的线程数也将大于线程池的最大线程数,则这个任务将被拒绝,并采用相应的拒绝策略,下面就是一些拒绝策略
- RejectedExecutionHandler:当队列满并且达到了最大的线程数,采取的拒绝策略处理器,策略比如有:
- AbortPolicy:默认策略,不处理他们,直接抛出RejectedExecutionException异常
- CallerRunsPolicy:用调用者所在的线程执行任务
- DiscardPolicy:直接丢弃掉任务
- DiscardOldestPolicy:丢弃掉阻塞队列中最靠前的任务,执行当前任务
任务的执行方法:execute,源码如下:主要是三个步骤
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { // <1> if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { //<2> int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) //<3> reject(command); }
<1>:如果工作线程少于核心线程数,创建一个新的线程,调用addWorker时会自动检查运行状态runState和线程数量
<2>:检查运行状态,还需要双重检查是否可以新创建一个线程(判断线程池中是否有线程,没有就新增)和检查线程池的状态,否则添加到阻塞队列
<3> : 如果不能添加到队列,也超过了线程池最大数量,则采取拒绝策略
下面看看执行execute方法里面的addWorker添加线程的方法:
这个方法主要是用来在基于所给的线程池的状态和所给的核心线程数以及最大线程数,来判断是否需要新建一个线程worker(小声:这是源码里面的注释说的,哈哈哈哈)
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 && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { //<1> int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) //<2> break retry; c = ctl.get(); // Re-read ctl 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 { // <2> w = new Worker(firstTask); // a 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)) { // b if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); 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; }
<1>:首先判断是否超出线程限制,循环的进行compareAndIncrementWorkerCount进行添加线程个数,里面就是一个CAS方法
<2>:如果CAS方法能成功,也就是添加线程个数能成功,那么就开始真正的添加任务到线程池中,然后创建线程
a:新建一个线程worker
b:在并发的情况下,加一个lock锁,判断线程池的状态,如果没有Shutdown,或者正在运行状态,把这个新建的线程worker添加到工作线程集(worker set)中,如果添加成功,则开始启动线程执行任务(t.start())
总结
线程池利用他的线程复用性减少了创建线程和关闭线程的开销,一个线程worker可以处理多个任务,并且线程池提供了一些可调的参数以及可以扩展的接口,后面说说线程池的应用,比如newCachedThreadPool newSingleThreadPool等
若有收获,就点个赞吧
本文作者:hu_volsnow
本文链接:https://www.cnblogs.com/volsnow/p/15788183.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)