jdk-ThreadPoolExecutor(一)---基本变量和大致流程解析
来源:互联网 发布:手机淘宝的试用在哪里 编辑:程序博客网 时间:2024/06/05 00:10
之前测试CountDownLatch时,使用了线程池。
ExecutorService service = Executors.newFixedThreadPool(3);
service.submit(run);
service.shutdown();
基于此,今天来看看java给我们提供的线程池有哪些?作为码农,不能以会使用作为终极目标,了解源码觉得很有必要,通过源码,可以知道它的执行原理,可以知道哪些可为,哪些不可为,可以说这是内功,不是一两天就能就能练成的,而招式就是你的使用,没有内功的支持,即使你的招式再华丽,也没有威力,说不定一个小小的问题,就能威胁你的生命~~~~~~~~~~~~。所以内功和招式相辅相成,武侠里,有些人内功深厚,和谁比试都不会吃亏,单纯的招式不够看。。。题外话了。
翻开源码,可以看见java给我们提供的线程池 Executor,它是顶级接口,里面只有一个execute方法,所以它只是一个执行工具,真正的线程池接口是ExecutorService,作为面向接口编程的理解,那么它一定是指向它的实现类,它的实现类是由Executors里面的静态方法生成的。
翻看Executors没的静态方法,发现它们构建的对象其实是ThreadPoolExecutor,那么今天的主角就出来了,先来看看它吧,之前分析过队列的一些知识,CAS,看起来就不会费力了。
先来理解一下它里面的一些变量,对于下面的逻辑段理解有好处,内部使用了大量的位移操作,需要慢慢理解。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//COUNT_BITS允许的最大线程数所占的位数 32-3 = 29,最低就是0位,最大是29位private static final int COUNT_BITS = Integer.SIZE - 3;//CAPACITY 允许的最大线程数,1 * 2^29 - 1//00000000 00000000 00000000 00000001 其实也就是 1左移 29 位//00100000 00000000 00000000 00000000 再减去 1//000 11111 11111111 11111111 11111111//前面三位是状态位,后面29位是线程数目位(为什么会这么分是因为AtomicInteger这个复合型变量)private static final int CAPACITY = (1 << COUNT_BITS) - 1;// runState is stored in the high-order bits//-1在计算机内部是32个1//11111111 11111111 11111111 11111111 左移29位,//111 00000 00000000 00000000 00000000 那么这边可以看见状态位其实是由高3位决定的,RUNNING的状态是 111//该状态下线程池能接受新任务,并且可以运行队列中的任务private static final int RUNNING = -1 << COUNT_BITS;//00000000 00000000 00000000 00000000//000 00000 00000000 00000000 00000000 状态为 000//该状态下的线程池不再接受新任务,但仍可以执行队列中的任务private static final int SHUTDOWN = 0 << COUNT_BITS;//00000000 00000000 00000000 00000001//001 00000 00000000 00000000 00000000 状态为 001//以下状态下的线程池不再接受新任务,不再执行队列中的任务,而且要中断正在处理的任务private static final int STOP = 1 << COUNT_BITS;//00000000 00000000 00000000 00000010//010 00000 00000000 00000000 00000000 状态为 010private static final int TIDYING = 2 << COUNT_BITS;//00000000 00000000 00000000 00000011//011 00000 00000000 00000000 00000000 状态为 011private static final int TERMINATED = 3 << COUNT_BITS;// Packing and unpacking ctl// CAPACITY 为 000 11111 11111111 11111111 11111111// 取非操作 为 111 00000 00000000 00000000 00000000,这样的话再和c进行 与 操作,那么高3位的值就能原样保留,低29位置成0,那么c里面低29位全部为0private static int runStateOf(int c) { return c & ~CAPACITY; }// CAPACITY 为 000 11111 11111111 11111111 11111111// 获取workerCount,workerCount是低29位的值,高3位为0,不管.private static int workerCountOf(int c) { return c & CAPACITY; }//传入的rs表示线程池运行状态runState,其是高3位有值,低29位全部为0的int,//而wc则代表线程池中有效线程的数量workerCount,其为高3位全部为0,而低29位有值得int,//将runState和workerCount做或操作|处理,即用runState的高3位,workerCount的低29位填充的数字,//而默认传入的runState、workerCount分别为RUNNING和0。private static int ctlOf(int rs, int wc) { return rs | wc; }
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)) //关注此处,加入Worker return; c = ctl.get(); //失败之后尝试再次加入 } if (isRunning(c) && workQueue.offer(command)) { //workQueue为新增线程的队列 int recheck = ctl.get(); //成功加入之后再次检查状态 if (! isRunning(recheck) && remove(command)) reject(command); //如果状态不一致,remove掉 else if (workerCountOf(recheck) == 0) addWorker(null, false); //自增空任务 } else if (!addWorker(command, false)) reject(command); //直接reject掉 }
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())) //1. 当状态超过或者就是SHUTDOWN时,不再接收新进的线程 //2. 当状态为SHUTDOWN时, ! (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()) 这个状态有点复杂 //我们来看 需要 整个状态是 真 才能return false。 //因此 rs == SHUTDOWN && firstTask == null && workQueue.isEmpty() 这个状态需要为假才行 //那么在 rs 是 SHUTDOWN 的情况下 firstTask == null()为假,或者 !workQueue.isEmpty()为假就行 //2.1 firstTask == null()为假 ,就是当前任务存在 //2.2 workQueue.isEmpty()为真 ,任务队列空的 //就是当状态是SHUTDOWN时,当前任务存在及 任务队列为空或者任务队列有任务时,不允许添加进来 return false; for (;;) { //获取当前数量 int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) //大于允许的最大值,也是不允许添加进来 return false; //如果允许添加,CAS操作将Count+1 if (compareAndIncrementWorkerCount(c)) break retry; //跳出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; //Work是个内部类,暂时不分析结构,新建处的线程都是加入到Worker内 try { final ReentrantLock mainLock = this.mainLock; w = new Worker(firstTask); //new出Worker,内部将状态设置成-1,继承自AQS,因此关联到AQS内去分析-1状态 final Thread t = w.thread; //获取内部创建的thread,也是通过将Runnable封装到Thread内返回 if (t != null) { //新建成功线程加锁 mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. //重新获取状态,以免状态发生变化 int c = ctl.get(); int rs = runStateOf(c); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { //1.这个地方其实就是在说在Running态下能新增 //2.或者在SHUTDOWN下但是新增任务是空的时候能进入,SHUTDOWN状态是不能新增任务的,但是需要执行线程池内剩余的任务 if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); //加入workers int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; //记录最大线程数 workerAdded = true; //将标志记录为true } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); //如果添加进入了,启动 workerStarted = true; //启动标志设置为true } } } finally { if (! workerStarted) //如果启动失败立即回退,从workers中删除,尝试结束线程 addWorkerFailed(w); } return workerStarted; }
private void addWorkerFailed(Worker w) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (w != null) workers.remove(w); //remove decrementWorkerCount(); //CAS减1 tryTerminate(); ///尝试结束 } finally { mainLock.unlock(); } } final void tryTerminate() { for (;;) { int c = ctl.get(); if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) //1. isRunning状态下直接返回 //2. c 状态 >=2 时,忽略 //3. 如果是SHUTDOWN时,只要队列任务不空,就返回不做操作 return; if (workerCountOf(c) != 0) { // Eligible to terminate interruptIdleWorkers(ONLY_ONE); //尝试去中断一个空闲线程。 return; } final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { try { terminated(); } finally { //如果此时线程数量是0 了,就将状态设置成TERMINATED ctl.set(ctlOf(TERMINATED, 0)); termination.signalAll(); //通知其他等待的线程 } return; } } finally { mainLock.unlock(); } // else retry on failed CAS } } private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; if (!t.isInterrupted() && w.tryLock()) { //注意此处,执行任务的线程是互斥的锁定对象,因此这边如果能获取到锁的话,可以认定是空闲线程 try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
本篇文章大致分析了一下execute的方法,从整个方法入手我们可以直观的看见一些东西,那我们来总结一下ThreadPoolExecutor的一些流程。
1.如果当前线程池有效线程数目小于corePoolSize的话,corePoolSize就是我们在构建时传入的允许的线程池的大小(包括正在运行的和空闲的线程数目)。那么就尝试添加新的worker线程处理command,调用有效线程数组时workerCountOf()方法,第二个参数true是作为边界值的约束条件使用,成功添加直接返回,否则再次获取clt的值c,也就是状态。
2.根据c判断出当前线程池是否为RUNNING状态。即既能接收新任务,又能处理队列中任务的状态,此时的c是重新获取的状态,多线程的处理是比较复杂的。通过offer方法加入到队列中,如果成功添加进入了队列中。那么需要再次获取状态,检查当前线程池的状态是不是RUNNING,这边再次做检查的目的,也就是为了判断出当做过一次添加线程进入队列之后,需要保证此时线程池任然处于一个有效状态。如果不是RUNNING状态的话,从队列中移除刚刚添加的线程,并且将当前任务拒绝掉。如果失败了,那么就在当前线程池中的数目是0时添加一个worker线程,但是不携带任务。
3.最后的逻辑在什么条件都不满足时,强行去新增一个线程处理任务,但是还是失败了直接拒绝掉。
整体理解的话不是很难啊,下一篇重点来分析内部的一些构造和一些具体的方法
- jdk-ThreadPoolExecutor(一)---基本变量和大致流程解析
- ThreadPoolExecutor核心实现原理和源码解析<一>
- ThreadPoolExecutor机制(一)--基本介绍
- 变量和基本类型一
- android 打包和签名 大致流程
- C++ 变量和基本类型(一)
- 0031 Java线程池(ThreadPoolExecutor)JDK源码解析【基础】
- JDK 源码解析 —— Executors ExecutorService ThreadPoolExecutor 线程池
- 友好 RxJava2.x 源码解析(一)基本订阅流程
- 网站解析基本流程
- 解析ThreadPoolExecutor
- 解析ThreadPoolExecutor
- ThreadPoolExecutor解析
- 参与github上开源项目的大致流程和注意事项
- 一、Python变量和数据类型(一) 基本数据类型
- Handler的大致流程
- tomcat6源码大致流程
- pdp激活大致流程
- 浅析云计算背景下云存储的优势与劣势
- Docker与Kubernetes系列(一): Docker的基本概念
- 常量池(constant_pool)
- JS 17-6-22
- 零配置-----6、MyBatisConfig
- jdk-ThreadPoolExecutor(一)---基本变量和大致流程解析
- Python load_entry_point 简述
- DOM文档加载的步骤
- 自省
- KEIL DEBUG调试记录
- MFC中窗口刷新函数详解
- C# 操作Excel大全
- 链表-leetcode 203. Remove Linked List Elements
- Linux下使Shell 命令脱离终端在后台运行