为了使页面渲染器实现更高的并发性,首先将渲染过程分成两个任务,一个是渲染所有的文本,另一个是下载所有的图像。(因为其中一个任务是CPU密集,另一个是I/O密集型,因此这种方式即使在单CPU系统上也能提升性能)。
Callable和Future有助于表示这些协同任务之间的交互。
public class FutureRenderer{
// 创建一个线程池
private final ExecutorService executor = ...;
void renderPage(CharSequence source) {
// 创建一个Callable来下载所有的图像
final List<ImageInfo> imageInfos = sanForImageInfo(source);
// 创建任务--task
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());
}
}
}
get方法拥有"状态依赖"的内在特性,因而调用者不需要知道任务的状态,此外在任务提交和获得结果中包含的安全发布属性也确保了这个方法是线程安全的。Future.get的异常处理代码将处理两个可能的问题:任务遇到一个Exception,或者调用get的线程在获得结果之前被中断。
FutureRenderer使得渲染文本任务与下载图像数据的任务并发地执行。当所有图像下载完后,会显示到页面上。这将提升用户体验,不仅使用户更快地看到结果,还有效利用了并行性。
6.3.4 在异构任务并行化中存在的局限
通过对异构任务进行并行化来获得重大的性能提升是很困难的。FutureRenderer使用了两个任务,如果其中渲染文本的速度远远高于下载图像的速度,那么程序的最终性能与串行执行的差别不大,而代码却变得更复杂了。
6.3.5 CompletionService; Executor与BlockingQueue
ExecutorService需要自己维护一个list来保存submit的call所返回的task结果Future。在主线程中遍历这个list并调用Future的get()方法取到Task的返回值。CompletionService将Executor和BlockingQueue的功能融合在一起。然后使用类似于take和poll等方法来获得已完成的结果,而这些结果会在完成时将被封装为Future。ExecutorCompletionService实现了CompletionService,并将计算部分委托给一个Executor。
// ExecutorCompletionService是CompletionService的一个实现类,在构造函数中创建一个BlockingQueue来保存计算结果。
public class ExecutorCompletionService<V> implements CompletionService<V> {
private final Executor executor;
private final AbstractExecutorService aes;
private final BlockingQueue<Future<V>> completionQueue;
...
// 两个构造函数
public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}
public ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue) {
if (executor == null || completionQueue == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
this.completionQueue = completionQueue;
}
// 包装了一下FutureTask
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
// 重写了done方法--将完成的结果放入到completionQueue中
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
// take和poll方法委托给了BlockingQueue,这些方法会在出结果之前阻塞
public Future<V> take() throws InterruptedException {
return completionQueue.take();
}
public Future<V> poll() {
return completionQueue.poll();
}
}
具体ExecutorCompletionService的示例如下:
import java.util.concurrent.*;
public class CompletionServiceDemo {
private int coreCpuNum;
private ExecutorService executor;
private CompletionService<Long> mcs;
public CompletionServiceDemo() {
coreCpuNum = Runtime.getRuntime().availableProcessors(); // 取得当前支持的核心数
System.out.println("this host has " + coreCpuNum + "Cpu(s)");
// 创建线程池
executor = Executors.newFixedThreadPool(coreCpuNum);
// 将线程池交由ExecutorCompletionService代理
mcs = new ExecutorCompletionService<Long>(executor);
}
// 计算任务
class CalculatorTask implements Callable<Long> {
int nums[];
int start;
int end;
public CalculatorTask(final int nums[], int start, int end) {
this.nums = nums;
this.start = start;
this.end = end;
}
@Override
public Long call() throws Exception {
long sum = 0;
for (int i = start; i < end; i++) {
sum += nums[i];
}
return sum;
}
}
private long getFinalSum() {
long sum = 0;
for (int i = 0; i < coreCpuNum; i++) {
try {
sum += mcs.take().get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
return sum;
}
// 分配任务
public long ParallelSum(int[] nums) {
int start, end, increment;
for (int i = 0; i < coreCpuNum; i++) {
increment = (nums.length / coreCpuNum) + 1;
start = i * increment;
end = start + increment;
if (end > nums.length) {
end = nums.length;
}
CalculatorTask mthread = new CalculatorTask(nums, start, end);
if (!executor.isShutdown()) {
// 提交执行
mcs.submit(mthread);
}
}
return getFinalSum();
}
//
public void close() {
executor.shutdown();
}
public static void main(String[] args) {
System.out.println("ExcutorServer thread pool demo show");
int[] nums = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
System.out.println("CompletionServiceDemo thread pool demo show");
CompletionServiceDemo mcom = new CompletionServiceDemo();
System.out.println("result per CompletionServiceDemo = "
+ mcom.ParallelSum(nums));
mcom.close();
}
}
6.3.6 使用CompletionService实现页面渲染器
通过CompletionService从两个方面来提高页面渲染器的性能:缩短总运行时间以及提高响应性。此外,通过从CompletionService中获取结果以及使得每张图片在下载完成后立即显示出来,能使用户获得一个更加动态和更高响应性的用户界面。
// 使用CompletionService,使元素在下载完成后立即显示出来
public class Render{
private final ExecutorService executor;
Renderer(ExecutorService executor) {
this.executor = executor;
}
void renderPage(CharSequence source){
List<ImageInfo> info = scanForImage(source);
CompletionService<ImageData> completionService = new ExecutorCompletionService<ImageData>(executor);
// 为每个图片创建一个任务
for (final ImageInfo : info)
// 用completionService提交任务
completionService.submit(new Callable<ImageData>(){
public ImageData call(){
return imageInfo.downloadImage();
}
});
renderText(source);
try {
for (int t = 0, n = info.size(); t < n; t ++){
// 从自身的completionQueue中取任务执行结果Future
Future<ImageData> f = completionService.take();
ImageData imageData = f.get();
renderImage(imageData);
}
} catch(InterruptedException e){
Thread.currentThread().inrerrupt();
} catch (ExecutionException e){
throw launderThrowable(e.getCause());
}
}
}
6.3.7 为任务设置时限
在使用限时任务时,当这些任务超时后应该立即停止,避免为继续计算一个不再使用的结果而浪费计算资源。
// 相当于controller
Page randerPageWithAd() throw InterruptedException{
long endNanos = System.nanoTime() + Time_BUDGET;
Future<Ad> f = exec.submit(new FetchAdTask());
// 在等待广告的同时显示页面
Page page = renderPageBody();
Ad ad;
try{
// 只等待指定的时间长度
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; // 返回加载广告后的页面
}
6.3.8 旅行预订门户网站
场景:
考虑一个旅行预定门户网站:用户输入旅行的日期和其他要求,门户网站获取并显示来自多条航线、旅店或者汽车租赁公司的报价。在这个过程中,可能会调用Web API服务、访问数据库、执行EDI事务或者其它网络请求计算等。在这种情况下,不宜让页面的响应时间受限于最慢的响应时间,而应该只显示在指定时间内收到的信息。对于没有及时响应的服务提供者,页面可以忽略它们。
具体分析:
从一个公司获得报价的过程与从其他公司获得报价的过程无关,因此可以将获取报价的过程当做一个任务,从而使得获取报价的过程能并发执行。
// 在预定的时间内请求旅游报价
// 创建子任务
private class QuoteTask implements Callable<TraveQuote> {
private final TravelCompany company;
private final TravelInfo travelInfo;
...
public TravelQuote call() throws Exception{
return company.solicitQuote(travelInfo);
}
}
public List<TravelQuote> getRankedTravelQuotes(TravelInfo travelInfo, Set<TravelCompany> companies, Comparator<TravelQuote> ranking, long time, TimeUnit unit) throws InterruptedException {
List<QuoteTask> tasks = new ArrayList<QuoteTask>();
for (TravelCompany company : companies)
tasks.add(new QuoteTask(company, travelInfo));
// 执行所有的任务
List<Future<TravelQuote>> futures = exec.invokeAll(tasks, time, unit);
// 创建一个结果列表
List<TravelQuote> quotes = new ArrayList<TravelQuote> (tasks.size());
Iterator<QuoteTask> taskIter = tasks.iterator();
for (Future<TravelQuote> f : futures){
QuoteTask task = taskIter.next();
try{
quotes.add(f.get());
} catch (ExecutionException e){
quotes.add(task.getFailureQuote(e.getCause()));
} catch (CancellationException e){
quotes.add(task.getTimeoutQuote(e));
}
}
Collections.sort(quotes, ranking);
return quotes;
}