面试被问到线程池是怎样实现线程复用的,如何回答?
如果你只知道线程池的几个参数,面试被问到再深一点的内容就回答不上来了,那么本系列文章带你熟悉线程池源码,更深入的了解线程池。
ThreadPoolExecutor将分几篇文章做解析,本文是第二篇,第一篇请见Java线程池只知道几个参数?来看看源码你就清楚了(一),关注一波,不错过后续内容,下面是核心知识点。本篇文章讲解Worker类、runWorker()、getTask()
一、Worker类详解
1.Worker类图
Worker是ThreadPoolExecutor的内部类,根据类图可以看出Worker继承了
AbstractQueuedSynchronizer类,实现了Runnable接口,也就是说,他本身就相当于一个同步队列,结合他的成员变量 thread 和 firstTask,可以知道他实际上就是我们线程池中所说的"线程"。除了父类 AQS 本身提供的独占锁以外,Worker 还提供了一些检查任务线程运行状态以及中断线程相关的方法。
此外,线程池中还有一个工作队列 workers,用于保存当前全部的 Worker:private final HashSet workers = new HashSet();
2.Worker类源码
本质上,Worker类既是一个同步组件,也是一个执行任务的线程。 所以如果有人问你线程池的线程是怎么包装的,至少我们现在知道有个Worker来包装而不是简单的Thread。下面分析下Worker源码:private final class Worker extends AbstractQueuedSynchronizer implements Runnable { // 工作线程 final Thread thread; // 初始任务,只在worker第一次执行任务的时候执行,之后都是从workQueue中获取任务执行 Runnable firstTask; // 线程执行过的任务数 volatile long completedTasks; //Worker类的构造方法,初始化任务并调用线程工厂创建执行任务的线程 Worker(Runnable firstTask) { setState(-1);// 调用runWorker()前禁止中断 this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } //重写Runnable接口的run()方法 public void run() { //调用ThreadPoolExecutor类的runWorker(Worker)方法 runWorker(this); } ... ... }
在Worker类的构造方法中,首先将同步状态state设置为-1,为了防止runWorker方法运行之前被中断。这是因为如果其他线程调用线程池的shutdownNow()方法时,如果Worker类中的state状态的值大于0,则会中断线程,如果state状态的值为-1,则不会中断线程。
Worker实现了Runable接口,在调用start()方法后,实际执行的是run方法,里面又调用了 ThreadPoolExecutor的runWorker()方法。下面我们来看看这个runWorker方法,知道线程池的线程是如何运行的。二、runWorker()方法分析final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // 新创建Worker时默认state为-1,AQS的unlock方法会将其改为0,此后允许使用interruptIfStarted()方法进行中断 // 完成任务以后是否需要移除当前Worker,即当前任务是否意外退出 boolean completedAbruptly = true; try { // 循环获取任务,后面解析getTask方法 while (task != null || (task = getTask()) != null) { // 加锁,防止 shundown 时中断正在运行的任务 w.lock(); // 如果线程池状态为 STOP 或更后面的状态,中断线程任务 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; // 任务执行完毕,完成数量+1 w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { // 根据completedAbruptly决定是否要移除意外退出的Worker,并补充新的Worker // 也就是说,如果上述过程顺利完成,工作线程没有挂掉,就不删除,下次继续用, // 否则就在workers中remove掉,然后再调用addWorker()方法添加。 //另外如果获取不到任务,getTask返回空,也会清楚该worker,起到回收非核心线程的目的 processWorkerExit(w, completedAbruptly); } }
概括一下runWorker方法的核心逻辑:
循环调用getTask方法,获取要执行的线程,加锁然后执行任务,如果执行完任务流程,并且没有发生异常导致 Worker 挂掉,就直接复用 Worker(在获取任务的方法 getTask()中循环等待任务),如果执行完任务流程后发现发生异常导致 Worker 挂掉,就从工作队列中移除当前 Worker,并且补充一个新的;
当然getTask方法就是从队列中获取的,来看下: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. // 如果线程池关闭了,且队列里的任务都完成了,或者线程池进入了比 STOP更大的状态 //,就不表示没有新任务 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } // 获取当前工作线程数 int wc = workerCountOf(c); // 时候设置了核心线程超时(默认false)或当前线程数大于核心线程数,及存在非核心线程, // 即判断当前当前是否需要进行超时控制 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; //如果线程池中的线程数量大于corePoolSize //获取大于corePoolSize或者是否正在等待执行任务并且轮询超时 //并且当前线程池中的线程数量大于1或者任务队列为空 if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { //成功减少线程池中的工作线程数量 if (compareAndDecrementWorkerCount(c)) return null; continue; } try { // 阻塞获取任务 Runnable r = timed ? // 阻塞 keepaliveTime 以获取任务,如果在 keepaliveTime 时间内没有获取到任务,则返回 null. workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; // 如果获取不到任务,说明非核心线程超时了,下一轮判断确认是否退出循环。 //退出循环后,runWorker方法执行完毕,会被processWorkerExit回收 timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
概括一下getTask方法:
判断线程池是否关闭,工作队列是否为空,如果是说明没任务了,直接返回null,否则接着往下判断,判断当前是否存在非核心线程,如果是说明需要进行超时处理,获取任务,如果不需要超时处理,则直接从任务队列获取任务,否则根据 keepaliveTime 阻塞一段时间后获取任务,如果获取不到,说明非核心线程超时,返回 null 交给 runWorker()中的processWorkerExit()方法去删除。
换句话说,runWorker()方法一旦执行完毕(非核心线程),必然会删除当前的 Worker,而通过 getTask()拿任务的 Worker(核心线程),在线程池正常运行的状态下,核心线程只会一直在 for 循环中等待直到拿到任务,而非核心线程超时以后拿不到任务就会返回一个 null,然后回到 runWorker()中走完processWorkerExit()方法被删除。三、Worker类总结
看了runWorker和getTask方法后,不知道你对这个Worker有没有进一步理解,那么这个Worker充当了什么作用呢,总结一下:
1.封装线程,将线程进行包装,与线程池的状态做关联。
2.通过worker达到线程复用的目的,while循环从队列中获取任务,达到核心线程一直运行,非核心线程运行有过期时间的目的,并且worker数量具有自我恢复能力(其实是重新创建worker),保证线程数量。
这篇就到这,后续文章会继续分析线程池的其他内容。
如果你觉得此文对你有一丁点帮助,点个赞,关个注,不失联, 还希望补充什么内容,大家在评论区留言,期待后续精彩内容。
微信搜索关注订阅号:马老司 ,学习更多技术点
赵心童咋了?丁俊晖曾公开质疑其实力水平,球迷丁主任看人太准近日,斯诺克英格兰公开赛吸引了不少粉丝的关注,在本次比赛中,丁俊晖和赵心童都发挥得不是很好,丁俊晖在31领先的情况下,被对手艾伦逆转取胜,令球迷们感到遗憾,而赵心童的表现更令人失望
办公室没人了工作怎么正常运行?试试蒲公英X5升级版的路由器!蒲公英X5升级版品牌其实提到蒲公英产品,经常使用电脑的朋友应该不陌生,我第一次接触这个产品的另一个支线产品向日葵而蒲公英的母公司则是海贝锐信息科技股份有限公司(简称贝锐科技),成立
93岁老人的生活智慧读书,是世上第一等好事有人问,在这个浮躁的时代,我们为什么还要读书?回答众说纷纭,其中一个回答是这样的读书与不读书,差的是一整个人生。不读书的人犹如井底之蛙,他们的认知范围局限在自己的过往经验和固有思维
情感散文,其实一个人,一辈子很长,一辈子很短悄然打开,好像只是为了给自己一个美丽的笑脸,这是心与心的距离。这世间,这一种情,这何堪,这一种刻骨的伤。今天,我还扯着电话的手机,不知他的身影还要继续上演,这一刻,他已经被抛弃,心
圣诞节洋美我对圣诞节的记忆,是从一群拥挤的人群中,快乐地抢到一块糖果开始的。记得那年我刚上小学,放学回家时,总是经过一座宏伟壮观的教堂。白墙肃静地围着红砖装扮着挺立的教堂,教堂上高高地耸
职场不相信眼泪,要哭回家哭头条创作挑战赛你连自己都不讨好,还指望别人来尊重你!醒醒吧!早点洗洗睡吧!无底线的付出,换来的是别人理所应当小小是我工作时认识的实习生,性格单纯活泼,对待身边所有人都很好。可我私底
再给纠缠你的人一次机会吧有一句广为流传的俗语便宜没好货,好货不便宜,意思是好的商品价钱不会便宜,价钱便宜的商品必定不好。在人与人的交往中,太过主动同样会显得很廉价,他却往往无法按捺内心的冲动,一次次厚着脸
男女间暧昧关系断联后,大多数人最真实的四个想法暧昧是什么?暧昧就是明明很刻意,但却偏偏要假装不经意明明很开心,却偏要假装不搭理明明很想要,却偏要假装开玩笑一个不去说清楚,一个装着不明白。暧昧这东西,你懂我的图谋不轨,我知你的故
120小时不关机实测极米H5提升到底多大,同价位天花板?我现在自己用的是极米H3S,极米H3S是再用完H5之后只能安慰自己说早买早享受,H3S在同价位已经是非常好的选择了,没想到H5升级这么多。极米H5是极米第一款采用CCB标准的投影仪
一加11R详细配置泄露,一加10价比百元机创感人纪录作为非旗舰机型,一加11R宣布将采用之前的旗舰芯片高通骁龙8,搭载LPDDR5内存和UFS3。1闪存。虽然高通发布了最新一代的高通8Gen2,但高通8在目前的手机市场上依然十分火爆
如何实现100家快递单轨迹推送?做电商的朋友们是不是会遇到以下的一些头疼的问题?由于天气大促爆仓等多种原因造成快递不能及时送达,也未及时通知客户而导致的客户流失及物流成本增加物流状态一直未更新,无法查询物流状态而