1. 任务
把程序抽象成多个任务
2. 现代 web 程序划分任务边界
以独立的客户请求为边界就是一个请求一个任务
3. 任务调度策略
3.1 串行的执行
糟糕的响应性和吞吐量
3.2 为每一个任务创建一个线程
结论:
任务交由子线程处理, 提高了响应性和吞吐量
任务处理的代码必须是线程安全的
不足:
线程生命周期的开销非常高
资源消耗可运行线程多于可用处理器的数量, 会有线程闲置占用内存, 且大量线程竞争 CPU 时将产生其他性能开销
稳定性不同平台可创建线程的数量有限制
4.Java 中 Executor 框架的设计
4.1 设计理念
Java 提供了 Executor 框架来执行任务基于生产者 - 消费者模式提交任务就是操作相当于生产者, 执行任务的线程相当于消费者(解耦, 削峰)
- public interface Executor {
- void execute(Runnable command);
- }
4.2 执行策略
任务的提交代码散布在整个程序的业务代码中
执行策略则统一交由框架处理
执行策略中定义了任务执行的 "What,Where,When,How" 等方面, 包括:
在什么 (What) 线程中执行任务?
任务按照什么 (What) 顺序执行(优先级)?
有多少个 (How Many) 任务能并发执行?
在队列中有多少个 (How Many) 任务在等待执行?
系统该怎么 (How) 拒绝任务?
在任务执行前后, 应该进行哪些 (What) 动作?
通过将任务提交与任务的执行策略分离, 有助于在部署阶段选择与可用硬件资源最匹配的执行策略
4.3 线程池
Executor 任务执行框架将 "为每一个任务分配一个线程" 策略编程基于线程池的策略
类库提供了一个灵活的线程池及一些有用的默认配置如 newFixedThreadpool
Web 服务器不会再高负载情况下失败
但是任务到达的速度总是超过任务执行的速度, 服务器仍有可能耗尽内存
4.4Executor 的生命周期
Executor 扩展了 ExecutorService 接口, 添加了一些用于生命周期管理的方法
- public interface ExecutorService extends Executor {
- /**
- * 平缓的关闭过程: 不再接受新任务, 等待已经提交的任务执行完成
- */
- void shutdown();
- /**
- * 粗暴的关闭过程: 它将尝试取消所有运行中的任务, 不在启动队列中尚未开始执行的任务
- */
- list<Runnable> shutdownNow();
- boolean isShutdown();
- boolean isTerminated();
- boolean awaitTermination(long timeout, TimeUnit unit);
- }
4.5 延迟任务和周期任务
JAVA 中提供 Timer 来管理定时任务
Timer 执行定时任务只会创建一个线程
Timer 是基于绝对时间的调度机制, 对系统时间敏感
Timer 存在线程泄露问题(Timer 不捕获异常, 当抛出一个未检查异常时线程将终止)
ScheduledThreadPoolExecutor 更优质的管理定时任务
其内部是一个线程池
其很好的解决了 Timer 的线程泄露问题
不适用于分布式环境
5. 找出可利用的并行性
本章提供一些示例来发掘在一个请求中的并行性
5.1 示例: 串行的页面渲染器
假设页面 = 文本标签 + 图片
如下代码串行的执行渲染
- public class SingleThreadRenderer {
- void renderPage(CharSequence source) {
- renderText(source);
- List<ImageData> imageData = new ArrayList<ImageData>();
- for (ImageInfo imageInfo : scanForImageInfo(source))
- imageData.add(imageInfo.downloadImage());
- for (ImageData data : imageData)
- renderImage(data);
- }
- }
5.2 携带结果的任务 Callable 与 Future
Runnable 作为基本的任务表现形式缺陷: 1. 无返回值 2. 不能抛出一个受检查异常
Callable 接口
它是任务更好的抽象, 描述了一个任务的返回值和异常
- public interface Callable<V> {
- V call() throws Exception;
- }
Future 接口
它表示一个任务的生命周期, 并提供了相应的方法来判断任务是否已经完成或取消
- public interface Future<V> {
- boolean cancel(boolean mayInterruptIfRunning);
- boolean isCancelled();
- boolean isDone();
- V get() throws Exception;
- V get(long timeout, TimeUnit unit);
- }
5.3 示例: 使用 Future 实现页面渲染器
将渲染过程分解成两个任务, 一个是渲染所有的文本, 一个是下载所有图像
代码略
渲染文本和渲染图片并发执行
5.4 在异构任务并行化中存在的局限
上例中一般渲染文本的速度远远高于渲染图片的速度, 程序最终和串行执行效率差别不大, 代码确变得更复杂了
只有大量相互独立且同构的任务可以并发进行处理时, 才能体现出性能的提升
5.5CompletionService:Executor 与 BlockingQueue
提交一组任务, 简单的写法
- @Test
- public void test() throws Exception{
- ExecutorService executor = Executors.newFixedThreadPool(5);
- List<Future<String>> futures = new ArrayList();
- for (int i=0; i<5; i++){
- final int param = i;
- Future<String> future = executor.submit(new Callable<String>() {
- @Override
- public String call() throws Exception {
- Thread.sleep(param * 1000);
- return "result" + param;
- }
- });
- futures.add(future);
- }
- for (int i=4; i>0; i--) {
- System.out.println(futures.get(i).get());
- }
- }
CompletionService 将 Executor 和 BlockingQueue 的功能融合你可以将 Callable 任务提交给它执行, 然后使用类似队列操作的 take 和 poll 方法来获得已完成的结果
5.6 示例: 使用 CompletionService 实现页面渲染器
书上的示例: 略
- @Test
- public void test() throws Exception{
- ExecutorService executor = Executors.newFixedThreadPool(5);
- CompletionService<String> completionService = new ExecutorCompletionService<>(executor);
- for (int i=4; i>0; i--){
- final int param = i;
- completionService.submit(new Callable<String>() {
- @Override
- public String call() throws Exception {
- Thread.sleep(param * 1000);
- return "result" + param;
- }
- });
- }
- for (int i=0; i<4; i++) {
- System.out.println(completionService.take().get());
- }
- }
输出:
- result1
- result2
- result3
- result4
5.7 为任务设置时限
为单个任务设置时间
- @Test
- public void singleTaskTest(){
- ExecutorService executor = Executors.newFixedThreadPool(5);
- Future<String> future = executor.submit(new Callable<String>() {
- @Override
- public String call() {
- try {
- Thread.sleep(2000L);
- }catch (Exception e){
- e.printStackTrace();
- }
- System.out.println("任务执行完毕...");
- return "singleTask.";
- }
- });
- try {
- System.out.println(future.get(1, TimeUnit.SECONDS));
- }catch (TimeoutException e){
- System.out.println("任务超时...");
- future.cancel(true); // 这句话的是否注销影响运行情况, 原理未知?
- }catch (InterruptedException e){
- e.printStackTrace();
- }catch (ExecutionException e){
- e.printStackTrace();
- }
- }
5.8 示例: 陆行预定门户网站
未多个任务设置超时时间
6. 总结
本章主要是介绍了 Java 的 Executor 框架的优点和一些常见需求
还有对任务的划分粒度, 要根据业务场景分析任务边界
来源: https://juejin.im/post/5a9e7714518825558001be64