CompletableFuture: 组合式异步编程(一)

580 阅读2分钟

场景:本地服务,需批量调用远程服务,各远程服务之间的结果互不影响。由于远程调用属于IO服务,因此调用服务越多,会导致响应结果越久

环境模拟

  1. 模拟远程接口
public interface RemoteLoader {
    /**
     * 远程接口方法
     */
    String load();
}
  1. 远程服务实现类
/**
 * 账户服务
 */
public class AccountService implements RemoteLoader {

    /**
     * 获取账户金额
     */
    @Override
    public String load() {
        MockUtils.delay();
        return MockUtils.getData().toString();
    }
}
/**
 * 人员信息服务
 */
public class EmployeeService implements RemoteLoader {

    /**
     * 获取入职时间信息
     *
     * @return
     */
    @Override
    public String load() {
        MockUtils.delay();
        return MockUtils.getInfo();
    }
}

异步方法比较

以下列出4种调用方式

  1. 同步方式;
  2. java8并行流;
  3. java7 future方式;
  4. Java8 CompletableFuture;

在使用future时,注意线程池的创建管理

模拟本地客户端,调用远程方法

/**
 * 模拟本地客户端,批量调用远程接口
 */
public class LocalClient {

    final int cores = Runtime.getRuntime().availableProcessors() * 2;
    /**
     * 远程接口集合
     */
    private final List<RemoteLoader> remoteLoaders = Arrays.asList(new AccountService(), new EmployeeService());
    /**
     * 创建线程池
     */
    private final ExecutorService executorService = Executors.newFixedThreadPool(remoteLoaders.size(), new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        }
    });
    private final ExecutorService executorService2 = new ThreadPoolExecutor(cores, cores, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1024),
            new NamedThreadFactory("test-executor", true), new ThreadPoolExecutor.AbortPolicy());

    /**
     * 同步方法
     */
    public List<String> findSequential() {
        return remoteLoaders.stream().map(RemoteLoader::load).collect(Collectors.toList());
    }

    /**
     * java8并行流
     */
    public List<String> findParallel() {
        return remoteLoaders.parallelStream().map(RemoteLoader::load).collect(Collectors.toList());
    }

    /**
     * java7 future
     */
    public List<String> findFuture() {
        // 整理future集合
        List<Future<String>> futureList = remoteLoaders.stream().map(loader -> executorService.submit(loader::load)).collect(Collectors.toList());
        // 从future获取各线程结果
        List<String> collect = futureList.stream().map(future -> {
            try {
                return future.get(2, TimeUnit.SECONDS);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 线程异常,关闭
                future.cancel(true);
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList());
        return collect;
    }

    /**
     * java8 CompletableFuture
     */
    public List<String> findCompletableFuture() {
        // 整理future集合
        List<CompletableFuture<String>> futureList = remoteLoaders.stream()
                .map(loader -> CompletableFuture.supplyAsync(loader::load, executorService2))
                .map(future -> future.thenApply(i -> i + "test"))
                .collect(Collectors.toList());
        // 从future获取各线程结果
        List<String> collect = futureList.stream().map(CompletableFuture::join).collect(Collectors.toList());
        return collect;
    }
}

测试对比响应时间

public class CompareMethodMain {

    private static final LocalClient localClient = new LocalClient();

    public static void main(String[] args) {
        execute("sync", () -> localClient.findSequential());
        execute("parallel", () -> localClient.findParallel());
        execute("future", () -> localClient.findFuture());
        execute("comFuture", () -> localClient.findCompletableFuture());
    }

    private static void execute(String msg, Supplier<List<String>> s) {
        long start = System.nanoTime();
        System.out.println("remote interface response = " + s.get());
        long duration = (System.nanoTime() - start) / 1_000_000;
        System.out.println(msg + " done in " + duration + " msecs");
    }
}

终端返回:

结论

  1. 在io密集型的方法里,建议使用CompletableFuture;
  2. 在计算密集型方法里,建议使用并行流;
  3. io密集型和计算密集型,在创建线程池时,核心数有区别;