java 并发任务_Java 并发实战 (六) 任务执行

本文介绍Java并发编程中任务的划分与执行策略,包括任务调度、线程池使用、任务并行化技巧等,并探讨了如何利用Executor框架提高程序响应性和吞吐量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java 并发实战 (六) 任务执行

1. 任务

把程序抽象成多个任务

2. 现代 web 程序划分任务边界

以独立的客户请求为边界就是一个请求一个任务

3. 任务调度策略

3.1 串行的执行

糟糕的响应性和吞吐量

3.2 为每一个任务创建一个线程

结论:

任务交由子线程处理, 提高了响应性和吞吐量

任务处理的代码必须是线程安全的

不足:

线程生命周期的开销非常高

资源消耗可运行线程多于可用处理器的数量, 会有线程闲置占用内存, 且大量线程竞争 CPU 时将产生其他性能开销

稳定性不同平台可创建线程的数量有限制

4.Java 中 Executor 框架的设计

4.1 设计理念

Java 提供了 Executor 框架来执行任务基于生产者 - 消费者模式提交任务就是操作相当于生产者, 执行任务的线程相当于消费者(解耦, 削峰)publicinterfaceExecutor{

voidexecute(Runnablecommand);

}

4.2 执行策略

任务的提交代码散布在整个程序的业务代码中

执行策略则统一交由框架处理

执行策略中定义了任务执行的 "What,Where,When,How" 等方面, 包括:

在什么 (What) 线程中执行任务?

任务按照什么 (What) 顺序执行(优先级)?

有多少个 (How Many) 任务能并发执行?

在队列中有多少个 (How Many) 任务在等待执行?

系统该怎么 (How) 拒绝任务?

在任务执行前后, 应该进行哪些 (What) 动作?

通过将任务提交与任务的执行策略分离, 有助于在部署阶段选择与可用硬件资源最匹配的执行策略

4.3 线程池

Executor 任务执行框架将 "为每一个任务分配一个线程" 策略编程基于线程池的策略

类库提供了一个灵活的线程池及一些有用的默认配置如 newFixedThreadpool

Web 服务器不会再高负载情况下失败

但是任务到达的速度总是超过任务执行的速度, 服务器仍有可能耗尽内存

4.4Executor 的生命周期

Executor 扩展了 ExecutorService 接口, 添加了一些用于生命周期管理的方法publicinterfaceExecutorServiceextendsExecutor{

/**

* 平缓的关闭过程: 不再接受新任务, 等待已经提交的任务执行完成

*/

voidshutdown();

/**

* 粗暴的关闭过程: 它将尝试取消所有运行中的任务, 不在启动队列中尚未开始执行的任务

*/

listshutdownNow();

booleanisShutdown();

booleanisTerminated();

booleanawaitTermination(longtimeout,TimeUnitunit);

}

4.5 延迟任务和周期任务

JAVA 中提供 Timer 来管理定时任务

Timer 执行定时任务只会创建一个线程

Timer 是基于绝对时间的调度机制, 对系统时间敏感

Timer 存在线程泄露问题(Timer 不捕获异常, 当抛出一个未检查异常时线程将终止)

ScheduledThreadPoolExecutor 更优质的管理定时任务

其内部是一个线程池

其很好的解决了 Timer 的线程泄露问题

不适用于分布式环境

5. 找出可利用的并行性

本章提供一些示例来发掘在一个请求中的并行性

5.1 示例: 串行的页面渲染器

假设页面 = 文本标签 + 图片

如下代码串行的执行渲染publicclassSingleThreadRenderer{

voidrenderPage(CharSequencesource){

renderText(source);

ListimageData=newArrayList();

for(ImageInfoimageInfo:scanForImageInfo(source))

imageData.add(imageInfo.downloadImage());

for(ImageDatadata:imageData)

renderImage(data);

}

}

5.2 携带结果的任务 Callable 与 Future

Runnable 作为基本的任务表现形式缺陷: 1. 无返回值 2. 不能抛出一个受检查异常

Callable 接口

它是任务更好的抽象, 描述了一个任务的返回值和异常publicinterfaceCallable{

V call()throwsException;

}

Future 接口

它表示一个任务的生命周期, 并提供了相应的方法来判断任务是否已经完成或取消publicinterfaceFuture{

booleancancel(booleanmayInterruptIfRunning);

booleanisCancelled();

booleanisDone();

Vget()throwsException;

Vget(longtimeout,TimeUnitunit);

}

5.3 示例: 使用 Future 实现页面渲染器

将渲染过程分解成两个任务, 一个是渲染所有的文本, 一个是下载所有图像

代码略

渲染文本和渲染图片并发执行

5.4 在异构任务并行化中存在的局限

上例中一般渲染文本的速度远远高于渲染图片的速度, 程序最终和串行执行效率差别不大, 代码确变得更复杂了

只有大量相互独立且同构的任务可以并发进行处理时, 才能体现出性能的提升

5.5CompletionService:Executor 与 BlockingQueue

提交一组任务, 简单的写法@Test

publicvoidtest()throwsException{

ExecutorServiceexecutor=Executors.newFixedThreadPool(5);

List>futures=newArrayList();

for(inti=0;i<5;i++){

finalintparam=i;

Futurefuture=executor.submit(newCallable(){

@Override

publicStringcall()throwsException{

Thread.sleep(param*1000);

return"result"+param;

}

});

futures.add(future);

}

for(inti=4;i>0;i--){

System.out.println(futures.get(i).get());

}

}

CompletionService 将 Executor 和 BlockingQueue 的功能融合你可以将 Callable 任务提交给它执行, 然后使用类似队列操作的 take 和 poll 方法来获得已完成的结果

5.6 示例: 使用 CompletionService 实现页面渲染器

书上的示例: 略@Test

publicvoidtest()throwsException{

ExecutorServiceexecutor=Executors.newFixedThreadPool(5);

CompletionServicecompletionService=newExecutorCompletionService<>(executor);

for(inti=4;i>0;i--){

finalintparam=i;

completionService.submit(newCallable(){

@Override

publicStringcall()throwsException{

Thread.sleep(param*1000);

return"result"+param;

}

});

}

for(inti=0;i<4;i++){

System.out.println(completionService.take().get());

}

}

输出:result1

result2

result3

result4

5.7 为任务设置时限

为单个任务设置时间@Test

publicvoidsingleTaskTest(){

ExecutorServiceexecutor=Executors.newFixedThreadPool(5);

Futurefuture=executor.submit(newCallable(){

@Override

publicStringcall(){

try{

Thread.sleep(2000L);

}catch(Exceptione){

e.printStackTrace();

}

System.out.println("任务执行完毕...");

return"singleTask.";

}

});

try{

System.out.println(future.get(1,TimeUnit.SECONDS));

}catch(TimeoutExceptione){

System.out.println("任务超时...");

future.cancel(true);// 这句话的是否注销影响运行情况, 原理未知?

}catch(InterruptedExceptione){

e.printStackTrace();

}catch(ExecutionExceptione){

e.printStackTrace();

}

}

5.8 示例: 陆行预定门户网站

未多个任务设置超时时间

6. 总结

本章主要是介绍了 Java 的 Executor 框架的优点和一些常见需求

还有对任务的划分粒度, 要根据业务场景分析任务边界

来源: https://juejin.im/post/5a9e7714518825558001be64

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值