通过多次优化实例来了解选择并发策略的正确姿势
通过模拟浏览器程序的渲染页面(Page-Rendering)功能,为了方便,假设HTML页面只会包含标签文本和图片以及URL;
第一个版本:串行加载页面元素
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); } }
存在的问题:浏览器加载图片之前需要下载图片,此时如果存在网络拥塞,那么此时的CPU几乎没怎么用,大都在等待I/O操作执行完成,也会使用户体验降低:图片没下载完,文字就加载不出来;
改进版本1:使用Future实现页面渲染
/** * @author YHW * @ClassName: FutureRenderer * @Description: * @date 2019/3/28 16:21 */ public class FutureRenderer { private 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{ List<ImageData> imageData = future.get(); for(ImageData data : imageData){ renderImage(data); } }catch(InterruptedException e){ Thread.currentThread().interrupt(); future.cancel(true); }catch(ExecutionException e){ throw launderThrowable(e.getCause()); } } }
该版本使得页面文本和图片实现异步加载,但还有可以优化的地方,假设渲染文本的速度远大于图片的下载速度(很有可能),那么该版本与串行程序最后的性能差别不大,所以此改进方法对于性能的提升非常有限,而代码却更加复杂,其实在大量相互独立且同构的任务可以并发进行处理时,才能体现出将程序的负载分配带来真正的性能提升;
改进版本2:使用完成服务(CompletionService),其基于Executor和BlockingQueue,可以将Callable任务交给它来执行,再使用类似队列的出队操作来获取结果:
public class Renderer { private final ExecutorService executor; Renderer(ExecutorService executor){ this.executor = executor; } void revderPage(Charquence source){ List<ImageInfo> info = scanForImageInfo(source); CompletionService<ImageData> completionService = new ExecutorComplementService<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++){ Future<ImageData> f = completionService.take(): ImageData imageData = f.get(); renderImage(imageData); } }catch(InterruptedException e){ Thread。currentThread().interrupt(); }catch(ExecutionException e){ throw launderThrowbale(e.getCause()); } } }
经过第二次的改进,页面更加“响应式”,每个图片都会在下载完成后直接加载渲染至页面,同时异步加载HTML中的文本和URL,使用户获得更加动态的界面;