1. 任务是否都要先放入队列?

    当工作线程数小于核心线程数时,任务是不会经过队列,而是直接创建 Worker 时传入。但是如果工作线程数已经大于核心线程数,则任务是要先放入队列的。实际上只要是被创建的工作线程所执行都是不需要经过工作队列的,而是在创建新工作线程时作为参数传入处理。对应就是调用 addWorker 方法的地方。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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);
}
  1. 什么时候创建额外的线程

    队列已经满了,并且当前工作线程数小于最大允许线程数才会创建额外的线程。实际上所有调用 addWorker 方法的地方都会经过该判断。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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);
}
// 队列队列已经满了,才会执行 addWorker 逻辑
else if (!addWorker(command, false))
reject(command);
} // addWorker 中的部分代码
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 (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 增加新的线程数
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
  1. 怎么销毁多余线程

    当队列中没有任务之后,执行的线程将会被销毁。
    final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// getTask() 返回为null,也就是队列中没有任务了
while (task != null || (task = getTask()) != null) {
// 省略代码
}
completedAbruptly = false;
} finally {
// 移除worker
processWorkerExit(w, completedAbruptly);
}
}
  1. 如何实现让多余线程在指定时间后销毁?

    超过核心线程数的线程,获取队列任务会使用 poll 方法,增加阻塞时间,如果在指定的时间没有任务到达,就会返回null,从而销毁该线程
    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.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
} int wc = workerCountOf(c); // Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
} try {
// poll 方法设置时间,也就是获取任务增加阻塞时间
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
  1. Worker 继承AQS的作用

    继承了AQS类,可以方便的实现工作线程的中止操作;
  2. 可以设置的最大线程数

    2^29 次方,因为ctl高三位被用于表示当前线程池的状态了,所以只有29位用于表示最大线程池的大小。
    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;
  1. 拒绝策略什么时候起作用?

    工作线程数已经达到 maximumPoolSize,并且队列已经满了,则会启用拒绝策略
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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);
}

欢迎转载,但请注明本文链接,谢谢你。

2019.04.21 17:47

ThreadPoolExecutor 几个疑惑与解答的更多相关文章

  1. [疑惑与解答] WxPython In Action -1

    在学<活学活用wxPython>第三章的时候,我遇到一点疑惑,那就是下面语句的区别是什么 例 3.1 第4,5行: panel = wx.Panel(self, -1) button = ...

  2. 关于 android 的 view.getLeft(), getRight(), getTop(), getBottom() 的一些疑惑(坑)解答

    (原创) 今天在做下滑刷新的时候碰到 view 的四个 get 函数有点特别,具体遇到的问题如下,经反复测试和查找资料,填坑如下: 1,为什么我有时候在使用getLeft(), getRight(), ...

  3. CAN总线疑惑与解答

    1    CAN总线2根数据线是怎么表示数据信息1和0的? Can总线采用差分数据表示方法,平时2个数据线为2.5V,表示隐性(1).当用数据0(显性)需要发送时1跟数据线上升到3.5V另一个下降到1 ...

  4. python新手中常见疑惑及解答

    1 lambda函数 函数格式是lambda keys:express   匿名函数lambda是一个表达式函数,接受keys参数,返回表达式的值.所以不用return,也没有函数名,经常用在需要ke ...

  5. java学习中碰到的疑惑和解答(二)

    路径问题是一个在平时学习和开发碰到的常见问题,对于初学者是一个比较值得研究的东西.因此对路径问题进行总结. 1. 编写路径为了告诉编译器如何找到其他资源.   2. 路径分类: 相对路径:从当前资源出 ...

  6. java学习中碰到的疑惑和解答(一)

    今天写一个接口的时候发现,接口的方法不需要写修饰符,直接写数据类型加上方法名(参数)即可通过编译. import java.util.List; import com.bjm.pojo.Flower; ...

  7. Oracle事务之一:锁和隔离

    Oracle事务之一:锁和隔离 一. 事务概述 事务管理是数据库处理的核心.数据库既要保证用户能并发地执行事务,还要保证数据库的一致性. 当第一条可执行的SQL开始执行,就隐形地开始了一个事务,直到遇 ...

  8. 初探Stage3D(二) 了解AGAL

    关于本文 本文并无打算事无巨细的介绍一遍AGAL,仅仅是对现有文档的一些理解及汇总,所以请先阅读相参考文档 AGAL概念 参考资料 http://www.adobe.com/devnet/flashp ...

  9. STM32之定时器

    一.定时器简介 1.时钟来源 2.定时器结构(以基本定时器为例) 二.基本定时器的编程方法 1.基本定时器的寄存器 2.例程 /** * @brief 定时器6的初始化,定时周期0.01s * @pa ...

随机推荐

  1. Octoroit OS VB操作系统简单介绍

    官方地址:http://octoroit.weebly.com/ 首先需要指明的是 :Octoroit OS 并不是一个真正意义上的操作系统,它是运行在windows 之上的 窗口系统,一个Visua ...

  2. VM Linux版本安装

    安装方法 https://www.jb51.net/softs/619194.html 1. 增加执行权限: sudo chmod +x VMware-Workstation-Full-14.1.2- ...

  3. Jedis路由key的算法剥离

    在Redis集群中,会有很多个分片,如果此时利用Jedis来操作此Redis集群,那么他会把数据路由到不到的分片上.而且如果动态的往集群中增加分片,也不会影响Jedis的功能.究竟是怎么做到的呢? 由 ...

  4. php 查询mysql数据批量转为PDF文件一(mac使用配置wkhtmltopdf html导出PDF)

    数据转标准PDF查文档,查资料先转HTML标准格式再html转PDF 转PDF wkhtmltopdf工具是最佳选择 首先下载wkhtmltopdf https://wkhtmltopdf.org/d ...

  5. 基于mykernel的时间片轮转调度

    学号: 363 原创作品,转载请注明出处.本实验资源来源: https://github.com/mengning/linuxkernel/ 一. 实验环境配置 本次实验在实验楼完成: 在实验楼的终端 ...

  6. GDAL——命令使用专题——gdallocationinfo命令

    GDAL——命令使用专题——gdallocationinfo命令  前言 GDAL(Geospatial Data Abstraction Library)是一个在X/MIT许可协议下的开源栅格空间数 ...

  7. MapServer Tutorial——MapServer7.2.1教程学习——第一节用例实践:Example1.5 Adding a raster layer

    MapServer Tutorial——MapServer7.2.1教程学习——第一节用例实践:Example1.5 Adding a  raster layer 一.前言 MapServer不仅支持 ...

  8. spark streamingcontext

    一个StreamingContext定义之后,必须做以下几件事情:1.通过创建输入DStream来创建输入数据源.2.通过对DStream定义transformation和output算子操作,来定义 ...

  9. [NOIP2014D2]

    T1 Problem 洛谷 Solution 枚举那个点的位置,再O(n)扫一遍求出覆盖的公共场合的数量... 所以时间复杂度为O(128 * 128 * n) Code #include<cm ...

  10. mysql 关于数据库和数据表的基本操作

    -- 备注: -- .每一条mysql语句后面都需要加上半角分号 -- .可以用``符号(1键旁边的那个键)将字段名称引用起来,如`Name` -- .mysql在windows下不区分大小写,在li ...