[本文是我对Java Concurrency In Practice C08的归纳和总结. 转载请注明作者和出处, 如有谬误, 欢迎在评论中指正. ]
task和线程池执行机制之间隐式的耦合
前面曾提到过, 线程池的应用解耦了task的提交和执行. 事实上, 这有所夸大, 因为不是所有的task都适用于所有的执行机制, 某些task要求在特定的线程池中执行:
1. 非独立task, 指的是依赖于其他task的任务.
2. 要求在单线程中运行的task. 某些task不是线程安全的, 无法并发运行. Executors.newSingleThreadExecutor()方法返回的线程池只包含单个线程, 提交给该线程池的task将缓存在一个无界队列中, 线程池中所包含的单个线程将依次从队列中取出task运行.
3. 响应时间敏感的task. 某些task要求必须在极短的时间内开始执行, 比如GUI应用中处理用户点击操作的task. 假如提交给某一线程池的task既包含long-running task, 也包含响应时间敏感的task, 那么响应时间敏感的task可能无法在极短的时间内得到执行.
4. 使用了ThreadLocal类的task. 线程池的标准实现可能会在空闲时销毁多余的线程, 繁忙时创建更多的线程, 更有可能重用线程. 所以使用了ThreadLocal的task不应该提交给线程池运行, 除非ThreadLocal的使用只限定在单个task内, 不用于多个task之间通信.
线程饥饿死锁
如果提交给线程池运行的task之间不是相互独立的, 就有可能出现线程饥饿死锁. 比如提交给SingleThreadExecutor执行的2个task, task A在执行过程中需要等待task B的执行结果才能继续, 而此时没有多余的线程用于执行task B, 如此就发生了线程饥饿死锁.
public class StarvationDeadLock {
public static void main(String[] args) {
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Runnable taskB = new Runnable() {
@Override
public void run() {
//...
}
};
Runnable taskA = new Runnable() {
@Override
public void run() {
Future<?> future = executor.submit(taskB);
try {
System.out.println("waiting for taskB complete");
// get方法将阻塞, 直到taskB执行完成
// 但是由于线程池中只有一个线程, 而该线程已经被taskA占用, 所以taskB将没有机会执行.
// 此时就发生了线程饥饿死锁
future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
e.printStackTrace();
}
//...
}
};
executor.submit(taskA);
}
}
不仅SingleThreadExecutor执行相互依赖的task时会发生死锁, 其他线程池执行相互依赖的task时也可能发生死锁:
public class StarvationDeadLock {
public static void main(String[] args) {
final ExecutorService executor = Executors.newFixedThreadPool(3);
// 设定await在Barrier对象上的线程数达到4个时, 其await方法才释放
final CyclicBarrier barrier = new CyclicBarrier(4);
// 重复提交4个task, 每个task都await在barrier对象上
// barrier的await方法将一直阻塞, 直到4个线程都到达await点.
// 但是线程池中只有3个线程, 不可能出现4个线程都达到await点的情形, 所以依然会发生死锁
for (int i = 0; i < 4; i++) {
executor.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println("waiting for other tasks arriving at common point");
barrier.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
}
}
避免相互依赖的task提交给同一线程池执行时发生死锁的唯一方法是: 线程池中的线程足够多.
确定线程池的size
如果线程池的size过大, 将造成内存等资源的浪费, 甚至使得资源耗尽. 如果线程池的size过小, 将造成CPU的利用率不高. 确定合适的size需要考虑:CPU数, 内存, 是计算密集型task还是I/O密集型task, 是否需要获取稀缺资源(比如数据库连接)等.
对于计算密集型task, 合适的size大约为CPU数量+1. 对于I/O占较大比例的task, 合适的size可以通过以下公式确定: size = CPU数量 * CPU利用率 * (1 + I/O时间比例). Runtime.getRuntime().availableProcessors()返回CPU的个数.
当然, 实际开发中size还受到内存, 文件句柄, socket, 数据库连接数等稀缺资源的约束. 将总的稀缺资源除以每一个task使用的资源数, 能得到线程数的上限.
循环并行化
如果循环体所进行的操作是相互独立的, 这样的循环可以并发的运行:
// 循环操作
void processSequentially(List<Element> elements) {
for (Element e : elements)
process(e);
}
// 将相互独立的循环操作转变为并发操作
void processInParallel(Executor exec, List<Element> elements) {
for (final Element e : elements) {
exec.execute(new Runnable() {
public void run() {
process(e);
}
});
}
exec.shutdown();
exec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
如果希望同时提交一系列task, 并且等待它们执行完毕, 可以调用ExecutorService.invokeAll方法.
如果希望task执行完毕之后就获取其执行结果, 可以使用CompletionService.
分享到:
相关推荐
在Go-grpc-task-execution-engine中,我们看到的是这个框架如何利用Go语言的并发特性以及gRPC的通信机制来构建一个任务执行平台。 1. **Go语言**:Go,也被称为Golang,是由Google开发的一种静态类型的、编译型的、...
软件项目管理-Execution-V200 软件项目管理-Execution-V200是软件项目管理的实施阶段,涉及项目计划的实施、执行和控制。项目实施阶段的主要活动包括按照计划实施各项工程活动和管理活动,获取工程活动的实施状态,...
任务执行服务(TES)API master分支状态: ... ga4gh.github.io/task-execution-schemas上托管的所有文档和页面均反映了master分支的最新API版本。 要监视最新的开发工作,请在上面的URL中添加“ preview / <br
"task-execution-framework" 是一个利用亚马逊 Web Services (AWS) 的组件,如 Elastic Compute Cloud (EC2)、Simple Queue Service (SQS) 和 DynamoDB 来实现任务执行框架的项目。这个框架以 Java 语言编写,旨在...
"making-sense-dependency-injection-test-execution-listener-源码"中可能包含了一个具体的DI框架(如Spring)与JUnit结合的示例。通过阅读源码,我们可以看到: - 如何配置和启用DI框架,以便在测试中注入依赖...
软件项目管理-Execution-V200.pptx
本文将深入探讨从PyPI官网下载的资源——`arcor2_execution-0.19.1.tar.gz`,分析其核心功能、应用场景以及与相关技术的集成,如Zookeeper、分布式系统和云原生(Cloud Native)环境。 首先,`arcor2_execution-...
资源分类:Python库 所属语言:Python 资源全名:pytest-execution-timer-0.1.0.tar.gz 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059
执行时间处理时间 node.js实用工具,用于测量代码中的执行时间安装npm install execution-time --save用法const perf = require ( 'execution-time' ) ( ) ;// At beginning of your codeperf . start ( ) ;// At ...
In order to solve the problem, this paper proposes a novel high level architecture support for automatic out-of-order (OoO) task execution on FPGA based heterogeneous MPSoCs. The architecture support...
执行跟踪查看器 执行跟踪查看器是一个用于查看,编辑和分析执行跟踪的应用程序。 它最初是为反向工程混淆代码而制作的,但可用于分析任何类型的执行跟踪。 特征 打开,编辑和保存执行跟踪 ... 仅支持阅读。 加载x64d
本压缩包文件"background-execution-commands.rar_execution_rontab"聚焦于如何在Linux系统中实现后台执行命令,包括使用`crontab`、`at`、后台运行以及`nohup`命令。 首先,`crontab`是Linux系统中的一个非常重要...
弹簧启动执行量度 测量关键代码块的执行时间,并将统计信息公开为执行器指标 提供一种轻量级的方法来测量(选定的)关键代码执行(例如...与AOP /注释一起使用 仅公开指标 @ExecutionMetric("some-action") public vo
向量化执行设计文档《Hive-Vectorized-Query-Execution-Design.pdf》对此进行了详细的阐述。 向量化执行的关键特点包括: 1. 批量处理:将数据行批量处理,即每次操作获取1024行数据,而不是一次只获取单行,这极...
《Pro Oracle SQL》是Oracle数据库查询优化的经典之作,第六章主要聚焦在Execution Plans(执行计划)上,这是数据库查询性能优化的关键。本章节的第四部分深入探讨了如何理解和解析执行计划,以及它对SQL性能的影响...
检查执行顺序一个工具和预提交挂钩,可自动检查Jupyter ... id : check-execution-order命令行示例$ check-execution-order bad.ipynbCell 1 comes after 2 in file 'bad.ipynb'配置还没有。 有任何要求吗? 请告诉我!
官方版本,亲测可用
JBPM4 中 ProcessDefinition、ProcessInstance、Execution、Task 关系和区别 ProcessDefinition 是流程的定义,也可以理解为流程的规范。它有一个 id,这个 id 的格式为 {key}-{version},其中 key 和 version 之间...
PS4-4.0x-Code-Execution-PoC, 我对 qwertyoruiopz 4.0 x的编辑利用来自http的PoC PS4 x 4.0代码执行这个 repo 是我对 4.0 x webkit的编辑,利用 qwertyoruiopz 发布。 edit组织。注释和在 3.50 4.07 ( 3.50,3.55,...
在2019年发布的LabVIEW桌面执行追踪工具包(Desktop Execution Trace Toolkit)19.0.0版本中,特别强调了高级信号处理功能,这为用户提供了更为精细化的程序性能分析和调试手段。 1. **桌面执行追踪工具包**: 这...