死磕 java线程系列之线程池深入解析——普通任务执行流程 (3)

这个方法比较简单,忽略状态检测和锁的内容,如果有第一个任务,就先执行之,之后再从任务队列中取任务来执行,获取任务是通过getTask()来进行的。

getTask()

从队列中获取任务的方法,里面包含了对线程池状态、空闲时间等的控制。

private Runnable getTask() { // 是否超时 boolean timedOut = false; // 死循环 for (;;) { int c = ctl.get(); int rs = runStateOf(c); // 线程池状态是SHUTDOWN的时候会把队列中的任务执行完直到队列为空 // 线程池状态是STOP时立即退出 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } // 工作线程数量// 【本文由公从号“彤哥读源码”原创】 int wc = workerCountOf(c); // 是否允许超时,有两种情况: // 1. 是允许核心线程数超时,这种就是说所有的线程都可能超时 // 2. 是工作线程数大于了核心数量,这种肯定是允许超时的 // 注意,非核心线程是一定允许超时的,这里的超时其实是指取任务超时 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 超时判断(还包含一些容错判断) if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { // 超时了,减少工作线程数量,并返回null if (compareAndDecrementWorkerCount(c)) return null; // 减少工作线程数量失败,则重试 continue; } try { // 真正取任务的地方 // 默认情况下,只有当工作线程数量大于核心线程数量时,才会调用poll()方法触发超时调用 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); // 取到任务了就正常返回 if (r != null) return r; // 没取到任务表明超时了,回到continue那个if中返回null timedOut = true; } catch (InterruptedException retry) { // 捕获到了中断异常 // 中断标记是在调用shutDown()或者shutDownNow()的时候设置进去的 // 此时,会回到for循环的第一个if处判断状态是否要返回null timedOut = false; } } }

注意,这里取任务会根据工作线程的数量判断是使用BlockingQueue的poll(timeout, unit)方法还是take()方法。

poll(timeout, unit)方法会在超时时返回null,如果timeout<=0,队列为空时直接返回null。

take()方法会一直阻塞直到取到任务或抛出中断异常。

所以,如果keepAliveTime设置为0,当任务队列为空时,非核心线程取不出来任务,会立即结束其生命周期。

默认情况下,是不允许核心线程超时的,但是可以通过下面这个方法设置使核心线程也可超时。

public void allowCoreThreadTimeOut(boolean value) { if (value && keepAliveTime <= 0) throw new IllegalArgumentException("Core threads must have nonzero keep alive times"); if (value != allowCoreThreadTimeOut) { allowCoreThreadTimeOut = value; if (value) interruptIdleWorkers(); } }

至此,线程池中任务的执行流程就结束了。

再看开篇问题

观察num值的打印信息,先是打印了0~4,再打印了10~14,最后打印了5~9,竟然不是按顺序打印的,为什么呢?

线程池的参数:核心数量5个,最大数量10个,任务队列5个。

答:执行前5个任务执行时,正好还不到核心数量,所以新建核心线程并执行了他们;

执行中间的5个任务时,已达到核心数量,所以他们先入队列;

执行后面5个任务时,已达核心数量且队列已满,所以新建非核心线程并执行了他们;

再执行最后5个任务时,线程池已达到满负荷状态,所以执行了拒绝策略。

总结

本章通过一个例子并结合线程池的重要方法我们一起分析了线程池中普通任务执行的流程。

(1)execute(),提交任务的方法,根据核心数量、任务队列大小、最大数量,分成四种情况判断任务应该往哪去;

(2)addWorker(),添加工作线程的方法,通过Worker内部类封装一个Thread实例维护工作线程的执行;

(3)runWorker(),真正执行任务的地方,先执行第一个任务,再源源不断从任务队列中取任务来执行;

(4)getTask(),真正从队列取任务的地方,默认情况下,根据工作线程数量与核心数量的关系判断使用队列的poll()还是take()方法,keepAliveTime参数也是在这里使用的。

彩蛋

核心线程和非核心线程有什么区别?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzfgw.html