多线程进阶003 之 可利用的并行性案例

学习书籍推荐《Java Concurrency In Practice》

在大多数服务器应用程序中都存在一个明显的任务边界: 单个客户请求.
但是有时候,任务边界并非是显而易见的,例如在很多桌面应用程序中,即使是服务器应用程序,在单个客户请求中,仍然可能存在可发掘的并行性,例如数据库服务器.

非常糟糕的Timer行为

一个异常将导致后面所有的任务,包括与该任务无关的任务都无法执行

import java.util.Timer;
import java.util.TimerTask;

public class OutOfTime {
    public static void main(String[] args) throws Exception {
        Timer timer = new Timer();
        timer.schedule(new ThrowTask(), 1);
        Thread.sleep(1);
        timer.schedule(new ThrowTask(), 1);
        Thread.sleep(500);
    }
    static class ThrowTask extends TimerTask{
        @Override
        public void run() {
            throw new RuntimeException();
        }
    }
}
import com.sun.scenario.effect.ImageData;

public class ImageInfo{

    public ImageData downloadImage() {
        return null;
    }

}

串行HTML页面渲染器

先绘制文本元素,同时为图像预留出矩形的站位空间,在处理完第一遍文本后,程序再开始下载图像,并将它们绘制到相应的占位空间中.

import java.util.ArrayList;
import java.util.List;

import com.sun.scenario.effect.ImageData;

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);
        }
    }

    private void renderImage(ImageData data) {

    }

    private List<ImageInfo> scanForImageInfo(CharSequence source) {
        List <ImageInfo> list = new ArrayList<ImageInfo>();
        /**
         * 转换操作
         */
        return list;
    }

    private void renderText(CharSequence source) {

    }
}

图像下载过程的大部分时间都是在等待I/O操作执行完成,在这期间CPU几乎不做任何工作,因此,这种串行执行方法没有充分地利用CPU,使得用户在看到最终页面之前要等待过长的时间.通过将问题分解为多个独立的任务并发执行,能够获得更高的CPU利用率和响应灵敏度.

使用Future实现页面渲染器

Future有助于表示这些协同任务之间的交互

public class FutureRenderer {
    private final ExecutorService executor = Executors.newFixedThreadPool(30);
    void renderPage(CharSequence source){
        final List<ImageInfo> imageInfos = scanForImageInfo(source);
        Callable<List<ImageData>> task = new Callable<List<ImageData>>(){
            @Override
            public List<ImageData> call() throws Exception {
                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) {
            try {
                throw landerThrowable(e.getCause());
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

    private Exception landerThrowable(Throwable cause) {
        return null;
    }

    private void renderImage(ImageData data) {
        // TODO Auto-generated method stub

    }
    private void renderText(CharSequence source) {
        // TODO Auto-generated method stub

    }
    private List<ImageInfo> scanForImageInfo(CharSequence source) {
        List <ImageInfo> list = new ArrayList<ImageInfo>();
        /**
         * 转换操作
         */
        return list;
    }
}

FutureRenderer使用了两个任务,其中一个负责渲染文本,另一个负责下载图像,如果渲染文本的速度远远高于下载图像速度,那么程序的最终性能与串行执行时的性能差别不大,而代码却变得更复杂了.

使用CompletionService实现页面渲染

如果向Executor提交了一组计算任务,并且希望在计算完成后获得结果,那么可以保留与每个任务关联的Future,然后反复使用get方法,同时将参数timeout指定为0,从而通过轮询来判断任务是否完成.这种方法虽然可行,但却有些繁琐,幸运的是,还有一种更好的方法,通过CompletionService来完成服务.

CompletionService将Executor和BlockingQueue的功能融合在一起.

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() throws Exception {
                    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) {
            try {
                throw landerThrowable(e.getCause());
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

为任务设置时限

有时候,如果某个任务无法在指定时间内完成,那么将不再需要它的结果,此时可以放弃这个任务.
在使用显示任务时需要注意,当这些任务超时后应该立即停止,从而避免为继续计算一个不再使用的结果而浪费计算资源.

在指定时间内获取广告信息:

Page renderPageWithAd() throws 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(ExecutoinException e){
        ad = DEFAULT_AD;
    }catch(TimeoutExecption e){
        ad = DEFAULT_AD;
        f.cancel(true);
    }
    page.setAd(ad);
    return page;
}

示例: 旅行预订门户网站

“预定时间” 方法可以很容易地扩展到任意数量的任务上,考虑这样一个旅行预订门户网站: 用户输入旅行日期的时间和其他要求,门户网站获取并显示来自多条航线,旅店或汽车租赁公司的报价.在获取不同公司报价过程中,可能会调用web服务,访问数据库,执行一个EDI事务或其他机制.在这种情况下,不宜让页面的响应时间首先于最慢的响应时间,而应该只显示在指定时间内收到的信息.

在预定时间内请求旅游报价:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class Travel {

    private final ExecutorService exec = Executors.newFixedThreadPool(30);

    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;
    }
}
class QuoteTask implements Callable<TravelQuote>{
    private final TravelCompany company;
    private final TravelInfo travelInfo;

    public QuoteTask(TravelCompany company,TravelInfo travelInfo) {
        this.company = company;
        this.travelInfo = travelInfo;
    }

    public TravelQuote getTimeoutQuote(CancellationException e) {
        // TODO Auto-generated method stub
        return null;
    }

    public TravelQuote getFailureQuote(Throwable cause) {
        // TODO Auto-generated method stub
        return null;
    }

    public TravelQuote call() throws Exception{
        return company.solicitQuote(travelInfo);
    }
}

class TravelQuote{

}

class TravelCompany{

    public TravelQuote solicitQuote(TravelInfo traveInfo) {

        return null;
    }

}

class TravelInfo{

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值