`
coolxing
  • 浏览: 874925 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
9a45b66b-c585-3a35-8680-2e466b75e3f8
Java Concurre...
浏览量:97565
社区版块
存档分类
最新评论

改善并发性能--JCIP6.3读书笔记

阅读更多

[本文是我对Java Concurrency In Practice 6.3的归纳和总结.  转载请注明作者和出处,  如有谬误, 欢迎在评论中指正. ]

浏览器的页面渲染模块负责HTML标记的处理, 本文以页面渲染为例探讨线程与并发. 为了简化问题, 我们假设只包含文本标记和图片标记.

 

单线程渲染

使用单线程处理是最简单的方式: 从头至尾扫描HTML文件, 如果遇到文本标记, 将其写入缓冲. 如果遇到图片标记, 就从Internet上下载后将其写入缓冲. 处理完整个文件之后, 将结果呈现给用户. 如果图片的下载速度很慢, 可能需要让用户等待很长时间. 因此我们对上述的渲染器进行简单的优化: 遇到图片标记, 就记录其下载地址, 并使用矩形的占位符. 处理完文件后先将结果呈现给用户, 然后再从Internet上下载图片, 图片下载完成就填充到占位符中:

public class SingleThreadRenderer { 
    void renderPage(CharSequence source) { 
	// 处理文本标记, 如果遇到图片标记使用占位符替代
        renderText(source); 
        List<ImageData> imageData = new ArrayList<ImageData>();
	// 从Internet上下载图片
        for (ImageInfo imageInfo : scanForImageInfo(source)) 
            imageData.add(imageInfo.downloadImage());
	// 将下载完成的图片填充到占位符中
        for (ImageData data : imageData) 
            renderImage(data); 
    } 
}

在下载图片的过程中, 需要等待网络I/O, 在此期间, CPU没有得到充分的利用.

 

分步渲染

为了充分利用CPU资源, 并减少用户的等待时间, 我们将渲染拆分为2个任务: 一个任务负责渲染文本(主要占用CPU资源), 一个任务负责下载图片(主要占用网络I/O资源):

public class FutureRenderer { 
    private final ExecutorService executor = ...; 
 
    void renderPage(CharSequence source) { 
        final List<ImageInfo> imageInfos = scanForImageInfo(source); 
        Callable<List<ImageData>> task = 
                new Callable<List<ImageData>>() { 
                    public List<ImageData> call() { 
                        List<ImageData> result 
                                = new ArrayList<ImageData>(); 
                        for (ImageInfo imageInfo : imageInfos) 
                            result.add(imageInfo.downloadImage()); 
                        return result; 
                    } 
                }; 
 
        Future<List<ImageData>> future =  executor.submit(task); 
	// 渲染文本
        renderText(source); 
 
        try { 
            // get方法将阻塞, 直到task完成下载
            List<ImageData> imageData =  future.get(); 
            for (ImageData data : imageData) 
		// 渲染图片
                renderImage(data); 
        } catch (InterruptedException e) { 
            // Re-assert the thread's interrupted status 
            Thread.currentThread().interrupt(); 
            // We don't need the result, so cancel the task too 
            future.cancel(true); 
        } catch (ExecutionException e) { 
            throw launderThrowable(e.getCause()); 
        } 
    } 
}

FutureRenderer先扫描文件, 找出所有的图片标记. 然后启动下载线程的同时进行文本渲染. 当文本渲染完成后调用future对象的get方法获取图片下载线程的下载结果. 如果调用get方法时下载任务尚未完成, get方法将阻塞, 直到下载完成, 或者抛出异常.

 

不对称任务的分析

FutureRenderer将渲染拆分为2个任务, 但是很有可能发生的是, 文本渲染任务很快就完成了, 但是下载所有图片的任务需要更长的时间. 这样的2个任务可以看做是不对称任务. 并发的引入导致问题比单线程时复杂很多, 而且并发并非是没有代价的, 因此拆分任务的时候一定要考虑拆分所带来的性能改善是否能够弥补其导致的损失. 

 

CompletionService介绍

CompletionService组合了Executor和BlockingQueue的功能, ExecutorCompletionService实现了CompletionService接口. 当向ExecutorCompletionService对象提交任务时, 将任务包装成QueueingFuture对象, 然后再委托给ExecutorCompletionService内部的Executor执行:

public Future<V> submit(Callable<V> task) {
	if (task == null) throw new NullPointerException();
	RunnableFuture<V> f = newTaskFor(task);
	// 将任务包装成QueueingFuture对象后委托给executor执行
	executor.execute(new QueueingFuture(f));
	return f;
} 

QueueingFuture是FutureTask的子类, 其覆盖了FutureTask的done方法, done方法由系统在任务执行完成之后回调:

private class QueueingFuture extends FutureTask<Void> {
	QueueingFuture(RunnableFuture<V> task) {
		super(task, null);
		this.task = task;
	}
	// 任务完成时将Future添加到已完成队列中
	protected void done() { completionQueue.add(task); }
	private final Future<V> task;
}

调用ExecutorCompletionService的take和poll方法可以从已完成队列中取出Future对象:

public Future<V> take() throws InterruptedException {
	return completionQueue.take();
}

public Future<V> poll() {
	return completionQueue.poll();
}
 

使用ExecutorCompletionService改进页面渲染

FutureRenderer将渲染拆分为2个不对称的任务, 此次我们将渲染拆分成多个任务: 一个图片下载一个任务:

public class Renderer { 
    private final ExecutorService executor; 
 
    Renderer(ExecutorService executor) { this.executor = executor; } 
 
    void renderPage(CharSequence source) { 
        final List<ImageInfo> info = scanForImageInfo(source); 
        CompletionService<ImageData> completionService = 
            new ExecutorCompletionService<ImageData>(executor); 
        for (final ImageInfo imageInfo : info) 
	    // 将图片下载拆分为多个任务
            completionService.submit(new Callable<ImageData>() { 
                 public ImageData call() { 
                     return imageInfo.downloadImage(); 
                 } 
            }); 
 
        renderText(source); 
 
        try { 
            for (int t = 0, n =  info.size(); t < n;  t++) { 
		// take方法可能阻塞: 当已完成队列中为空时
                Future<ImageData> f = completionService.take(); 
		// get方法不会阻塞, 因为从take方法返回的Future对象肯定是已完成的
                ImageData imageData = f.get(); 
                renderImage(imageData); 
            } 
        } catch (InterruptedException e) { 
            Thread.currentThread().interrupt(); 
        } catch (ExecutionException e) { 
            throw launderThrowable(e.getCause()); 
        } 
    } 
} 
 

Renderer将图片下载拆分成多个任务, 解决了任务的不对称问题. 当图片下载完成后, 会以完成的顺序将Future添加到CompletionService对象的已完成队列中. 调用CompletionService对象的take方法可以从已完成队列中取出Future, 如果队列为空, take方法将阻塞, 直到队列不为空. 因此Renderer对图片的渲染按照下载完成的顺序进行(并非按照提交下载任务的顺序进行). Renderer具有不错的并发性能, 并且改善了渲染的响应速度.

2
1
分享到:
评论

相关推荐

    Embarcadero 最新 Dev-Cpp 6.3版 包含TDM-GCC_9.2

    在Dev-Cpp 6.3中集成TDM-GCC 9.2意味着开发者可以利用最新的GCC编译器特性,如C++17标准的支持,这包括更高效的内存管理、新的并发模型以及更丰富的模板功能。同时,这个版本还可能包含性能改进和错误修复,使得代码...

    mingw64位编译器(gcc6.3)

    5. **并发编程工具**:C++14引入了`std::async`、`std::future`和`std::promise`等并发编程工具,使得在C++中进行多线程和异步编程变得更加容易。 使用MingW64位编译器时,你需要了解如何配置环境变量,以便系统...

    Serv-U6.3服务器端软件

    6. **性能优化**: Serv-U6.3在性能方面进行了优化,支持并发连接,可以处理大量用户同时访问。它还支持大文件传输,提高了数据传输效率。 7. **故障恢复与备份**: Serv-U的配置信息可以导出备份,当服务器出现...

    Java并发实践-学习笔记

    10. **并发性能优化**:笔记会探讨如何通过优化并发策略来提高程序性能,例如减少锁的竞争、合理使用并发工具以及避免活锁和死锁。 11. **案例分析与实战**:笔记可能包含实际的并发编程案例,帮助读者更好地将理论...

    javalist源码-jcip-code-listings:《Java并发实践》一书中的源代码

    list原始代码实践中的Java并发-代码清单 创建时间:2020年11月9日晚上11:13网址: 由Brian Goetz和Tim Peierls在JCP JSR-166专家组成员的协助下撰写,并已发布到公共领域,如所解释。 请注意,Creative Commons不再...

    Serv-U_6.3

    6. **性能优化**:Serv-U具备良好的性能优化选项,如缓存策略、并发连接数控制,确保服务器在高负载下依然稳定运行。 7. **移动设备支持**:Serv-U兼容各种桌面和移动设备的FTP客户端,使用户无论身处何处都能便捷...

    Super-EC6.3

    3. **性能优化**:从“Super”一词我们可以推断,这个版本可能在性能上有显著提升,比如更快的处理速度、更低的资源消耗、更高的并发处理能力,这些都是企业级系统非常关注的指标。 4. **稳定性与可靠性**:在企业...

    并发--并发的一些理论知识

    并发--并发的一些理论知识 资源源于不但搜索,自由源于不但努力

    Java并发编程---synchronized关键字

    Java并发编程---synchronized关键

    十、并发通信 -并发通信

    并发通信 -并发通信

    十一、并发池-并发池-并发池

    并发池并发池并发池并发池并发池并发池并发池

    【书籍学习】Netty、Redis、Zookeeper高并发实战-netty-redis-zookeeper.zip

    【书籍学习】Netty、Redis、Zookeeper高并发实战-netty-redis-zookeeper # netty-redis-zookeeper 【书籍学习】Netty、Redis、Zookeeper高并发实战

    Java并发编程与高并发解决方案-学习笔记-www.itmuch.com.pdf

    本文将基于文档《Java并发编程与高并发解决方案-学习笔记***.pdf》中提供的内容,来详细阐述并发编程和高并发的基本概念、CPU多级缓存与缓存一致性、以及Java内存模型。 ### 并发与高并发概念 在现代多线程编程中...

    读书笔记-Java并发编程实战-基础篇

    5. 正确性和线程安全性:编写并发应用程序的步骤包括保证程序正确性和性能测试,如果需要,再对程序进行优化。无状态的类天生就是线程安全的。 6. 竞态条件和原子操作:竞态条件是由于线程执行顺序的不确定性导致...

    serve-u 6.3

    7. **性能优化**:在6.3版本中,Serve-U可能已经进行了性能优化,能高效处理大量并发连接,确保在高负载下依然保持稳定运行。 8. **跨平台兼容**:Serve-U通常支持Windows操作系统,但具体到6.3版本是否支持其他...

    java并发编程实战源码-jcip-examples:java并发编程实战

    研究这些容器的源码,有助于理解它们在并发访问下的性能和安全性。 6. **死锁、活锁与饥饿**:并发编程中,线程间的交互可能导致这些不良状态。源码实例将帮助你识别并避免这些问题。 7. **线程通信**:通过使用...

    Java并发编程-并发容器1

    HashTable虽然提供了线程安全,但其同步机制导致并发性能较低。为了解决这些问题,Java引入了ConcurrentHashMap,它在保证线程安全的同时提供了较高的并发性能。 **关键属性** 1. **table**: `transient volatile ...

    Java并发编程-设计原则与模式

    Java并发编程-设计原则与模式 pdf格式

    java高级技术JUC高并发编程教程2021(1.5G)

    java高级技术JUC高并发编程教程2021(1.5G) 〖课程介绍〗: java高级技术JUC高并发编程教程2021(1.5G) 〖课程目录〗:   01-JUC高并发编程-课程介绍.mp4 02-JUC高并发编程-JUC概述和进程线程概念(1).mp4 03-JUC...

    阿里专家级并发编程架构师课程 彻底解决JAVA并发编程疑难杂症 JAVA并发编程高级教程

    阿里专家级并发编程架构师级课程,完成课程的学习可以帮助同学们解决非常多的JAVA并发编程疑难杂症,极大的提高JAVA并发编程的效率。课程内容包括了JAVA手写线程池,UC线程池API详解,线程安全根因详解,锁与原子类...

Global site tag (gtag.js) - Google Analytics