改善并发性能--JCIP6.3读书笔记
[本文是我对Java Concurrency In Practice 6.3的归纳和总结. ?转载请注明作者和出处, ?如有谬误, 欢迎在评论中指正. ]
浏览器的页面渲染模块负责HTML标记的处理, 本文以页面渲染为例探讨线程与并发. 为了简化问题, 我们假设只包含文本标记和图片标记.
?
单线程渲染
使用单线程处理是最简单的方式: 从头至尾扫描HTML文件, 如果遇到文本标记, 将其写入缓冲. 如果遇到图片标记, 就从Internet上下载后将其写入缓冲. 处理完整个文件之后, 将结果呈现给用户. 如果图片的下载速度很慢, 可能需要让用户等待很长时间. 因此我们对上述的渲染器进行简单的优化: 遇到图片标记, 就记录其下载地址, 并使用矩形的占位符. 处理完文件后先将结果呈现给用户, 然后再从Internet上下载图片, 图片下载完成就填充到占位符中:
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具有不错的并发性能, 并且改善了渲染的响应速度.