-
使用Executor就必须要将任务表述为一个Runnable, 就__必须有明确的任务边界__。
对于服务器应用程序, 自然边界就是一个客户请求, 但其实可以继续细分, 挖掘潜在的并行性
-
Callable和Future
(1) Runnable的__局限性__在于, 不能返回值或抛出一个受检查异常
(2) 为了解决Runnable的局限性, Callable是一种更好的抽象: 它返回一个值, 并且可以抛出异常
public interface Callable<V> { V call() throws Exception; }
(3) Future表示一个任务的生命周期, 并且提供了相应的方法来判断是否已经完成或取消、获取任务的结果和取消任务等。在Future规范中隐含的表示了, 任务的生命周期只能前进不能后退
(4) Future中的get()方法取决于任务的状态(尚未开始、正在运行、已完成)
如果任务没有完成, 将会阻塞;
如果任务已经完成, 将会立即返回结果或者抛出Exception: 如果任务抛出异常, 则get会将异常封装为ExecutionException重新抛出; 如果任务被取消, 会抛出CancellationException
(5) 创建一个Future任务的方法
1° ExecutorService中的所有submit()都会返回一个Future
2° 显示的创建一个FutureTask实例, 因为FutureTask实现了Future和Runnable接口(见"FutureTask.uml"), 所以可以传给Executor或者run
-
浏览器的页面渲染器示例
(1) 不好的串行示例
public abstract 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); } } interface ImageData { } interface ImageInfo { ImageData downloadImage(); } abstract void renderText(CharSequence s); abstract List<ImageInfo> scanForImageInfo(CharSequence s); abstract void renderImage(ImageData i); }
(2) 使用Future实现
public abstract class FutureRenderer { private final ExecutorService executor = Executors.newCachedThreadPool(); 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) { // 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()); } } interface ImageData { } interface ImageInfo { ImageData downloadImage(); } abstract void renderText(CharSequence s); abstract List<ImageInfo> scanForImageInfo(CharSequence s); abstract void renderImage(ImageData i); }
这个示例把下载并得到图片数据作为单独一个任务submit给executor, 然后需要渲染的时候用Future对象get(), 实现了并行性
但是, 这样做的问题是两个任务的完成时间不一样, 渲染文本的任务(主线程)是个快操作, 下载图片的操作是个慢操作, 相当于快操作早早完成再等待慢操作, 并没有性能上的显著提升
(3) 使用CompletionService
为了解决(2)存在的问题, 可以继续将任务分解: 把每张图片的下载过程都分解为一个任务;
这样相当于给executor提交了很多个任务, 每个任务都关联一个Future对象,使用轮询的方式判断任务有没有完成,这样做可行但是繁琐;
一种好的解决方式是使用CompletionService, 它关联一个Executor对象, 并且可以take()方法__获得已经完成的结果__,并且这些结果在完成时会被封装成Future返回。
示例
public abstract 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++) { Future<ImageData> f = completionService.take(); ImageData imageData = f.get(); renderImage(imageData); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (ExecutionException e) { throw launderThrowable(e.getCause()); } } interface ImageData { } interface ImageInfo { ImageData downloadImage(); } abstract void renderText(CharSequence s); abstract List<ImageInfo> scanForImageInfo(CharSequence s); abstract void renderImage(ImageData i); }
注意CompletionService的构造函数, 事实上多个CompletionService对象可以共享一个Executor对象。 而所有线程池都是Executor对象, 也就是说不会因为创建了多个线程池导致内存上耗尽
-
为任务设置时限
(1) 有这样一种需求: 让一个任务在指定时间内做完, 如果超出了指定的时限就使用默认的结果
满足这样需求的是Future对象的带限时的get()方法
V get(long timeout, TimeUnit unit)
如果超时则抛出TimeoutException异常
(2) 注意任务超时后应该立刻停止这个任务, 此时可以使用Future的cancel()方法
(3) 示例: 加载广告, 如果超时则放上默认的广告
public class RenderWithTimeBudget { private static final Ad DEFAULT_AD = new Ad(); private static final long TIME_BUDGET = 1000; private static final ExecutorService exec = Executors.newCachedThreadPool(); Page renderPageWithAd() throws InterruptedException { long endNanos = System.nanoTime() + TIME_BUDGET; Future<Ad> f = exec.submit(new FetchAdTask()); // Render the page while waiting for the ad Page page = renderPageBody(); Ad ad; try { // Only wait for the remaining time budget long timeLeft = endNanos - System.nanoTime(); ad = f.get(timeLeft, NANOSECONDS); } catch (ExecutionException e) { ad = DEFAULT_AD; } catch (TimeoutException e) { ad = DEFAULT_AD; f.cancel(true); } page.setAd(ad); return page; } private Page renderPageBody() { return new Page(); } static class Ad { } static class Page { public void setAd(Ad ad) { } } static class FetchAdTask implements Callable<Ad> { public Ad call() { return new Ad(); } } }
添加了ad = f.get(timeLeft, NANOSECONDS);用于在指定时间内获得结果, 如果超时则抛出TimeoutException: 捕获这个异常并取消这个任务f.cancel(true);
chapter06_任务执行_3_找出可利用的并行性
最新推荐文章于 2023-10-16 15:43:48 发布