JAVA多线程问题 — 任务执行Future机制与CompletionService

11 篇文章 0 订阅
3 篇文章 0 订阅
1、异步任务Callable

Executor框架使用Runnable作为基本的任务表示形式,但是Runnable有一定的局限性:不能返回一个值或抛出一个受检查的异常。实际中很多任务都是存在延迟的,如执行数据库查询、从网络上获取资源或执行复杂计算先进,对于这样的任务,Callable是一种更好的抽象:它认为入口(call)将返回一个值并可能抛出一个异常。

callable用法和runnable一样,只不过调用的是call方法,该方法有一个泛型返回值类型,你可以任意指定。

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     * 存在返回值
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

线程是属于异步计算模型,所以你不可能直接从别的线程中得到函数返回值。这时候,Future就出场了。Future可以监视目标线程调用call的情况,当你调用Future的get()方法以获得结果时,当前线程就开始阻塞,直接call方法结束返回结果。

2、任务获取Future

表示异步计算的结果,它提供了检查计算是否完成、等待计算的完成,并获取计算的结果的方法。其get方法用来获取结果,在未完成之前调用该方法将会阻塞直到完成。取消任务的继续执行则用cancel方法。还提供了一些其他方法,以确定任务是正常完成还是被取消了。一旦任务完成,就不能再取消了。如果仅仅是将Future用于可取消的用途,而对任务执行结果不关心的话,可以声明为Future<?>的形式,并且直接返回 null 作为底层任务的执行结果。Future形式中的V就是get方法的返回类型。

Future提供的主要方法:

 //试图取消对此任务的执行。 
 boolean cancel(boolean mayInterruptIfRunning) 
 //get方法的行为取决于任务的状态(尚未开始、正在运行、已完成)。
 //如果任务已经完成,那么get会立即返回或者抛出一个Exception,
 //如果任务没有完成,那么get将阻塞并直到任务完成。   
 V get() 
 //如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。       
 V get(long timeout, TimeUnit unit)        
 //如果在任务正常完成前将其取消,则返回 true。 
 boolean isCancelled() 
 //如果任务已完成,则返回 true。 
 boolean isDone() 

Future用法示例:

interface ArchiveSearcher { 
     String search(String target); 
} 
class App {
	 private ArchiveSearcher searcher;
     ExecutorService executor = Executors.newFixedThreadPool(5); 
     void showSearch(final String target) throws InterruptedException {
         //异步执行搜索任务
         Future future = executor.submit(new Callable() {
             public String call() {
                 return searcher.search(target);
             }});
         displayOtherThings(); // 搜索时做其他事情
         try {
             displayText(future.get()); // 获取搜索结果
         } catch (ExecutionException ex) { 
             cleanup();  //异常清理工作
             return; 
         }
    }
 }

还可以显式地为某个指定的Runnable或Callable实例化一个FutureTask(由于FutureTask实现了Runnable,因此可以将它提交给Executor来执行或者直接调用它的run方法),所以示例中的任务提交也可以换成这样:

//将搜索构造成FutureTask
FutureTask<String> future = new FutureTask(new Callable() {
   public String call() {
       return searcher.search(target);
   }});
executor.execute(future); //提交FutureTask执行
3、CompletionService

场景:如果向Executor提交了一组计算任务,并且希望在计算完成后获取结果;

这里有一种更好的方式来实现对任意一个线程运行完成后的结果都能及时获取的办法:使用CompletionService,它内部添加了阻塞队列BlockingQueue,从而获取future中的值,然后根据返回值做对应的处理。

原理:内部通过阻塞队列+FutureTask,实现了任务先完成可优先获取到,即结果按照完成先后顺序排序。

建议:使用率也挺高,而且能按照完成先后排序,建议如果有排序需求的优先使用。只是多线程并发执行任务结果归集,也可以使用。

一般future使用和CompletionService使用的两个测试案例如下:

public class Renderer {
 
    private final ExecutorService executorService;
 
    public Renderer(ExecutorService executorService) {
        this.executorService = executorService;
    }
 
    public void renderPage(CharSequence source) {
        final List<ImageInfo> imageInfoList = scanForImageInfo(source);
        CompletionService<ImageData> completionService = new ExecutorCompletionService<ImageData>(executorService);
        for (final ImageInfo imageInfo : imageInfoList) {
            completionService.submit(new Callable<ImageData>() {
                public ImageData call() throws Exception {
                    return imageInfo.downloadImage();
                }
            });
        }
 
        renderText(source);
 
        try {
            for (int i = 0, n = imageInfoList.size(); i < n; i++) {
            	//采用completionService.take(),内部维护阻塞队列,任务先完成的先获取到
                Future<ImageData> f = completionService.take();
                ImageData imageData = f.get();
                renderImage(data);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            future.cancel(true);
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
 
    }
}
4、JDK8::CompletableFuture

JDK1.8才新加入的一个实现类,实现了Future, CompletionStage2个接口;

当一个Future可能需要显示地完成时,使用CompletionStage接口去支持完成时触发的函数和操作。当2个以上线程同时尝试完成、异常完成、取消一个CompletableFuture时,只有一个能成功。

CompletableFuture实现了CompletionStage接口的如下策略:

  1. 为了完成当前的CompletableFuture接口或者其他完成方法的回调函数的线程,提供了非异步的完成操作。

  2. 没有显式入参Executor的所有async方法都使用ForkJoinPool.commonPool()为了简化监视、调试和跟踪,所有生成的异步任务都是标记接口AsynchronousCompletionTask的实例。

  3. 所有的CompletionStage方法都是独立于其他共有方法实现的,因此一个方法的行为不会受到子类中其他方法的覆盖。

CompletableFuture实现了Future接口的如下策略:

  1. CompletableFuture无法直接控制完成,所以cancel操作被视为是另一种异常完成形式。方法isCompletedExceptionally可以用来确定一个CompletableFuture是否以任何异常的方式完成。

  2. 以一个CompletionException为例,方法get()和get(long,TimeUnit)抛出一个ExecutionException,对应CompletionException。为了在大多数上下文中简化用法,这个类还定义了方法join()和getNow,而不是直接在这些情况中直接抛出CompletionException。

具体使用demo:

public class CompletableFutureDemo {
    public static void main(String[] args) {
        Long start = System.currentTimeMillis();
        //结果集
        List<String> list = new ArrayList<String>();
        List<String> list2 = new ArrayList<String>();
        //定长10线程池
        ExecutorService exs = Executors.newFixedThreadPool(10);
        List<CompletableFuture<String>> futureList = new ArrayList<>();
        final List<Integer> taskList = Lists.newArrayList(2,1,3,4,5,6,7,8,9,10);
        try {
            //方式一:循环创建CompletableFuture list,调用sequence()组装返回一个有返回值的CompletableFuture,返回结果get()获取
            for(int i=0;i<taskList.size();i++){
                final int j=i;
                //异步执行 , calc计算任务方法
                CompletableFuture<String> future = CompletableFuture.supplyAsync(()->calc(taskList.get(j)), exs)
                    //Integer转换字符串    thenAccept只接受不返回不影响结果
                    .thenApply(e->Integer.toString(e))
                    //如需获取任务完成先后顺序,此处代码即可
                    .whenComplete((v, e) -> {
                        System.out.println("任务"+v+"完成!result="+v+",异常 e="+e+","+new Date());
                        list2.add(v);
                    })
                    ;
                futureList.add(future);
            }
            //流式获取结果:此处是根据任务添加顺序获取的结果
            list = sequence(futureList).get();

            //方式二:全流式处理转换成CompletableFuture[]+组装成一个无返回值CompletableFuture,join等待执行完毕。返回结果whenComplete获取
            //calc计算任务方法
            CompletableFuture[] cfs = taskList.stream().map(object-> CompletableFuture.supplyAsync(()->calc(object), exs)
                    .thenApply(h->Integer.toString(h))
                    //如需获取任务完成先后顺序,此处代码即可
                    .whenComplete((v, e) -> {
                        System.out.println("任务"+v+"完成!result="+v+",异常 e="+e+","+new Date());
                        list2.add(v);
                    })).toArray(CompletableFuture[]::new);
            //等待总任务完成,但是封装后无返回值,必须自己whenComplete()获取
            CompletableFuture.allOf(cfs).join();
            System.out.println("任务完成先后顺序,结果list2="+list2+";任务提交顺序,结果list="+list+",耗时="+(System.currentTimeMillis()-start));
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            exs.shutdown();
        }
    }
/**
 *
 * @Description 组合多个CompletableFuture为一个CompletableFuture,所有子任务全部完成,组合后的任务才会完成。带返回值,可直接get.
 * @param futures List
 * @return
 * @since JDK1.8
 */
public static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
    //1.构造一个空CompletableFuture,子任务数为入参任务list size
    CompletableFuture<Void> allDoneFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
    //2.流式(总任务完成后,每个子任务join取结果,后转换为list)
    return allDoneFuture.thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList()));
}

/**
 *
 * @Description Stream流式类型futures转换成一个CompletableFuture,所有子任务全部完成,组合后的任务才会完成。带返回值,可直接get.
 * @param futures Stream
 * @return
 * @since JDK1.8
 */
public static <T> CompletableFuture<List<T>> sequence(Stream<CompletableFuture<T>> futures) {
    List<CompletableFuture<T>> futureList = futures.filter(f -> f != null).collect(Collectors.toList());
    return sequence(futureList);
}
5、总结:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RachelHwang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值