并发编程九-Callable/Future和CompletableFuture介绍

一、概述

在业务需求中,很多时候异步执行某个任务之后是需要获取到执行结果的,但是使用继承Thread类和实现Runnable接口的方法来异步执行任务时,想要得到执行结果往往都不是很方便。那么除了这两种方式可以用来执行异步任务之外,还有一种方式可是实现,并且还可以简单的就获取到异步执行的结果,那就是Callable。

在使用Callable的同时一般都会使用Future来配合获取执行结果。

二、使用

现在有一个简单的业务需求:根据条件去多个服务器上获取资源,然后把资源放到一起,比如说根据图片编号去10台服务器上取出与该编号相关的10张图片

1,使用Runnable来实现

实现:创建10个线程,每个线程去一台服务器上获取图片资源,然后把获取到的结果方法一个线程安全的集合中,等待10个线程执行完成,就可以从这个集合中取出想要的结果

代码:

import java.util.*;

/**
 * 使用runnable异步获取多个资源
 */
public class SearchImageDemo1 {
    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        String key = "image";
        int searchCount = 10;// 10个资源存放点
        // 用于存放线程
        List<Thread> threadList = new ArrayList<>();
        // 用于存放查询结果
        List<String> resList = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < searchCount; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 查询并返回结果,把结果放入集合中
                        String data = getData(key);
                        resList.add(data);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"Thread-"+i);
            thread.start();
            threadList.add(thread);
        }
        // 等待10个线程执行结束后程序才继续执行
        for (Thread thread : threadList) {
            thread.join();
        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:"+(end-start)+"ms");
        System.out.println(resList.toString());// 输出查询结果
    }

    // 获取资源的方法
    private static String getData(String str) throws InterruptedException {
        long l = new Random().nextInt(3000);
        Thread.sleep(l);// 模拟http请求耗时操作
        System.out.println(Thread.currentThread().getName()+"耗时:"+l);
        return str+":"+ UUID.randomUUID().toString();
    }
}

2,使用Callable和FutureTask实现

实现:FutureTask是属于Future的一个实现类,同时它来实现了Runnable接口,所以可以把FutureTask放到Thread中执行。

代码:

import java.util.*;
import java.util.concurrent.*;

/**
 * 使用Callable和FutureTask实现
 */
public class SearchImageDemo2 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        String key = "image";
        int searchCount = 10;// 10个资源存放点
        // 用于存放Future
        List<FutureTask> futureTaskList = new ArrayList<>();
        // 用于存放查询结果
        List<String> resList = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < searchCount; i++) {
            FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return getData(key);
                }
            });
            futureTaskList.add(futureTask);
            Thread thread = new Thread(futureTask,"Thread-"+i);
            thread.start();
        }
        // 收集10个线程执行的结果,并把结果放入到集合中
        for (FutureTask<String> futureTask : futureTaskList) {
            String res = futureTask.get();
            resList.add(res);
        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:"+(end-start)+"ms");
        System.out.println(resList.toString());// 输出查询结果
    }

    // 获取资源的方法
    private static String getData(String str) throws InterruptedException {
        long l = new Random().nextInt(3000);
        Thread.sleep(l);
        System.out.println(Thread.currentThread().getName()+"耗时:"+l);
        return str+":"+ UUID.randomUUID().toString();
    }
}

3,使用线程池、Callable和Future实现

实现:使用线程池的submit()方法,该方法接收一个Callable实现类,把任务交由线程池来执行,然后从Future中获取执行结果。

代码:

import java.util.*;
import java.util.concurrent.*;

/**
 * 使用线程池、Callable和Future实现
 */
public class SearchImageDemo3 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        long start = System.currentTimeMillis();
        String key = "image";
        int searchCount = 10;// 10个资源存放点
        // 用于存放线程
        List<Future> futureTaskList = new ArrayList<>();
        // 用于存放查询结果
        List<String> resList = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < searchCount; i++) {
            Future<String> future = executorService.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return getData(key);
                }
            });
            futureTaskList.add(future);
        }
        // 收集10个线程执行的结果,并把结果放入到集合中
        for (Future<String> future : futureTaskList) {
            String res = future.get();
            resList.add(res);
        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:"+(end-start)+"ms");
        System.out.println(resList.toString());// 输出查询结果
        executorService.shutdown();// 关闭线程池
    }

    // 获取资源的方法
    private static String getData(String str) throws InterruptedException {
        long l = new Random().nextInt(3000);
        Thread.sleep(l);
        System.out.println(Thread.currentThread().getName()+"耗时:"+l);
        return str+":"+ UUID.randomUUID().toString();
    }
}

三、Future的方法

get():会阻塞线程,直到任务执行完毕

get(long timeout,TimeUnit unit):可以设置等待超时时间,如果在指定时间内任务还没有返回结果,那么取消阻塞等待。

cancel(boolean mayInterruptIfRunning):方法可以用来停止一个任务,如果任务可以停止(通过mayInterruptIfRunning来进行判断),则可以返回true,如果任务已经完成或者已经停止,或者这个任务无法停止,则会返回false.

isDone():判断当前方法是否完成

isCancel():判断当前方法是否取消

四、使用CompletableFuture

在jdk1.8中,提供了一个异步处理任务的工具

1,使用CompletableFuture实现上面的需求

import java.util.*;
import java.util.concurrent.*;

/**
 * 使用CompletableFuture实现
 */
public class SearchImageDemo4 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        String key = "image";
        int searchCount = 10;// 10个资源存放点
        // 用于执行结果
        CompletableFuture<String>[] futures = new CompletableFuture[searchCount];
        // 用于存放查询结果
        List<String> resList = Collections.synchronizedList(new ArrayList<>());
        // 启动10个任务异步执行
        for (int i=0;i<searchCount;i++) {
            futures[i] = CompletableFuture.supplyAsync(() -> {
                try {
                    return getData(key);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            });
        }
        // 获取这些执行结果
//        Stream.of(futures).forEach((f)->{f.whenComplete((v,e)->{resList.add(v);}).join();});
        for (CompletableFuture<String> future : futures) {
            future.whenComplete((v,e)->{
                resList.add(v);
            }).join();
        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:"+(end-start)+"ms");
        System.out.println(resList.toString());// 输出查询结果
    }

    // 获取资源的方法
    private static String getData(String str) throws InterruptedException {
        long l = new Random().nextInt(3000);
        Thread.sleep(l);
        System.out.println(Thread.currentThread().getName()+"耗时:"+l);
        return str+":"+ UUID.randomUUID().toString();
    }
}

2,CompletableFuture介绍

2.1 创建一个CompletableFuture异步操作对象

有两个方法:

runAsync():接收一个Runnable对象,执行之后没有返回值。重载方法可以指定线程池,如果不传则默认使用ForkJoinPool.commonPool()作为线程池。

supplyAsync():接受Supplier对象,执行之后有返回值。重载方法可以指定线程池,如果不传则默认使用ForkJoinPool.commonPool()作为线程池。

2.2 获取执行结果

2.2.1 同步获取结果

使用get()或者join()方法获取结果

2.2.2 任务完成之后处理结果

whenComplete():使用当前执行任务的线程处理结果

whenCompleteAsync():使用线程池中的线程处理结果

exceptionally():返回一个新的CompletableFuture,它在CompletableFuture完成时完成,当它异常完成时,给定的异常函数会触发;如果这个CompletableFuture正常完成,那么返回的CompletableFuture也以相同的值正常完成。

handle():在任务执行完成后对结果进行处理,如果执行过程中发生异常,还可以对异常进行捕获并处理。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值