With CompletableFuture, making asynchronous programming is not so difficult!

This article REVIEW:

  • Business demand scenario describes
  • Technical design thinking
  • Future actual design patterns
  • CompletableFuture combat mode
  • CompletableFuture production proposal
  • CompletableFuture performance test
  • CompletableFuture Extended

1, business demand scenario describes


The same thing is always changing.

Presumably, everyone in their spare time, often watching videos, often with several APP, such as Youku, love Fantastic Art, Tencent.

These videos can be played only APP on the phone, but also to support playback on the TV.

APP played on television terminal is independent releases, with the end of the phone APP is not the same.

When we watch a movie, click into a movie, entered the album details page to page, this time, the player will automatically play the video. The user sees on the phone album details page, and see on television specials details page, the page design style is different.

Let's look at the visual effects.

Tencent video album details page on the phone:

End mobile phones album details page

Upper half of the shots, the following are recommended for you, star actor, peripheral recommendations, comments and other features.

Accordingly, at the end of the TV show album details page is not the same way. Product manager proposed a hypothesis needs to be revised requirements page for details.
Style requirements as shown below:

TV end album details page

Style comparison of the two terminals at the TV side album details page, contains a lot of sections, each section transverse to show more content.

Requirements on the product design is that some plates from the recommended content, and some plates from the search, the source of some sectors CMS (content management system). Simple to understand, for each section content from different sources, derived from the recommendation, search and other content requirement interface is near real-time requests.

2, technical design thinking


Taking into account this product mention demand, in fact, not difficult to implement.

The main part is divided into static data and dynamic data section, for infrequently changing data can be obtained via the static interface, available through the dynamic interface for near-real-time data.

Static interface design:

The album itself is the attribute data and video albums under generally do not change frequently.
Demand scenario in the introduction, I shot a movie channel. If a TV channel, will show a list of episodes (all videos in the album, such as Episode 1, Episode 2 ...), and update the video is generally less frequent, so the data in the album details page episode list you can get from a static interface.

Static interface data generation process:

Static interface design

The other part is the need to achieve dynamic interface, call the third party interface to obtain data, such as recommendations, search data.
At the same time, the requirements of content between the plate and the plate allowed to repeat.

Dynamic interface design:

Option One:

Serial call, that is in accordance with the display order of each section, call the appropriate interface to obtain third-party data.

Option II:

Concurrent calls, i.e. calls between a plurality of parallel plates, to improve the overall efficiency of the interface in response.

In fact, these two programs, each with advantages and disadvantages.

Option One serial calls, the benefits are simple development model, according to a serial fashion in turn call interface, content data deduplication, to aggregate all of the data back to the client.

However, the interface response time is dependent on third-party interface response time, usually a third-party interfaces are not always reliable, the interface might raised the overall response time, which led to the holding thread for too long, affecting the overall throughput of the interface.

Option Two parallel calls, in theory, can improve the overall interface response time, assuming that at the same time calling multiple third-party interfaces, depending on the response time of the slowest interface.

When invoked in parallel, taking into account the need to "pool technology," that can not be unlimited create too many threads on the JVM process. At the same time, also taking into account the contents of data between the plate and the plate, according to the order on the product design to make weight.

According to this demand scenario, we choose the second option to achieve more appropriate.

Option II is selected, we abstract a simplified model shown below:

Simple model

T1, T2, T3 represents a plurality of content blocks threads. Results returned first thread T1, T2 thread returns the results can not be repeated and the result of the content returned by the thread T1, T3 thread returns the results can not, T2 thread returns two results is redundant T1.

We consider the technical implementation, when the parallel call multiple third-party interfaces, interfaces need to get returns the result, the first thought is Future, enables asynchronous tasks get results.

In addition, JDK8 provides asynchronous get results CompletableFuture easy to use tools to solve the pain points on the Future of the use of some, in a more elegant way to achieve modular asynchronous programming, but also fit the functional programming.

3, Future Design Patterns combat


Future interface design:

Provides acquisition task results, cancel the task, determine the job status interface. Method call to get the results of the task, the task is not completed under the circumstances, it would result in a call blocking.

Future interface method provides:
`` `
// Get the task results
V get () throws InterruptedException, ExecutionException ;

// Get the timeout support task results
V GET (Long timeout, TimeUnit Unit)
throws InterruptedException, ExecutionException, TimeoutException;

// determine whether the task has been completed
boolean isDone ();

// determine whether the task has been canceled
boolean isCancelled ();

// cancel the task
boolean the Cancel (boolean mayInterruptIfRunning);
`` `

Usually, we take into account when using Future obtain task results will be used to implement FutureTask ThreadPoolExecutor or functional requirements.

ThreadPoolExecutor, FutureTask FIG relationship class and Future interface:

Future design class diagram

TheadPoolExecutor submit offers three methods:

// 1. 提交无需返回值的任务,Runnable 接口 run() 方法无返回值
public Future<?> submit(Runnable task) {
}

// 2. 提交需要返回值的任务,Callable 接口 call() 方法有返回值
public <T> Future<T> submit(Callable<T> task) {
}

// 3. 提交需要返回值的任务,任务结果是第二个参数 result 对象
public <T> Future<T> submit(Runnable task, T result) {
}

Submit third exemplary method is as follows:

static String x = "东升的思考";
public static void main(String[] args) throws Exception {
    ExecutorService executor = Executors.newFixedThreadPool(1);
    // 创建 Result 对象 r
    Result r = new Result();
    r.setName(x);

    // 提交任务
    Future<Result> future =
                    executor.submit(new Task(r), r);
    Result fr = future.get();

    // 下面等式成立
    System.out.println(fr == r);
    System.out.println(fr.getName() == x);
    System.out.println(fr.getNick() == x);
}

static class Result {
    private String name;
    private String nick;
    // ... ignore getter and setter 
}

static class Task implements Runnable {
    Result r;

    // 通过构造函数传入 result
    Task(Result r) {
            this.r = r;
    }

    @Override
    public void run() {
            // 可以操作 result
            String name = r.getName();
            r.setNick(name);
    }
}

The results are true.

FutureTask design and implementation:

Implements Runnable and Future two interfaces. Implements Runnable interface, it can be explained as the task object, submitted directly to ThreadPoolExecutor to perform. Future interface implements, instructions can be obtained to perform tasks return results.

According to the products we use simulation FutureTask two threads to achieve the following functions by example.
Note understood in conjunction with the sample code:

public static void main(String[] args) throws Exception {
    // 创建任务 T1 的 FutureTask,调用推荐接口获取数据
    FutureTask<String> ft1 = new FutureTask<>(new T1Task());
    // 创建任务 T1 的 FutureTask,调用搜索接口获取数据,依赖 T1 结果
    FutureTask<String> ft2  = new FutureTask<>(new T2Task(ft1));
    // 线程 T1 执行任务 ft1
    Thread T1 = new Thread(ft1);
    T1.start();
    // 线程 T2 执行任务 ft2
    Thread T2 = new Thread(ft2);
    T2.start();
    // 等待线程 T2 执行结果
    System.out.println(ft2.get());
}

// T1Task 调用推荐接口获取数据
static class T1Task implements Callable<String> {
    @Override
    public String call() throws Exception {
            System.out.println("T1: 调用推荐接口获取数据...");
            TimeUnit.SECONDS.sleep(1);

            System.out.println("T1: 得到推荐接口数据...");
            TimeUnit.SECONDS.sleep(10);
            return " [T1 板块数据] ";
    }
}
        
// T2Task 调用搜索接口数据,同时需要推荐接口数据
static class T2Task implements Callable<String> {
    FutureTask<String> ft1;

    // T2 任务需要 T1 任务的 FutureTask 返回结果去重
    T2Task(FutureTask<String> ft1) {
         this.ft1 = ft1;
    }

    @Override
    public String call() throws Exception {
        System.out.println("T2: 调用搜索接口获取数据...");
        TimeUnit.SECONDS.sleep(1);

        System.out.println("T2: 得到搜索接口的数据...");
        TimeUnit.SECONDS.sleep(5);
        // 获取 T2 线程的数据
        System.out.println("T2: 调用 T1.get() 接口获取推荐数据");
        String tf1 = ft1.get();
        System.out.println("T2: 获取到推荐接口数据:" + tf1);

        System.out.println("T2: 将 T1 与 T2 板块数据做去重处理");
        return "[T1 和 T2 板块数据聚合结果]";
    }
}

Execution results are as follows:

> Task :FutureTaskTest.main()
T1: 调用推荐接口获取数据...
T2: 调用搜索接口获取数据...
T1: 得到推荐接口数据...
T2: 得到搜索接口的数据...
T2: 调用 T1.get() 接口获取推荐数据
T2: 获取到推荐接口数据: [T1 板块数据] 
T2: 将 T1 与 T2 板块数据做去重处理
[T1 和 T2 板块数据聚合结果] 

summary:

Future represents the "future" means, mainly to the time-consuming task of some operations, to a separate thread to do it. So as to achieve the purpose asynchronous, submit the task of the current thread, after the submission of the results of the task and get the task during the current thread can continue to perform other operations that do not need to wait around to return the results.

4, CompleteableFuture real mode


For the Future design patterns, although we submit the task, do not enter any obstruction, but when the caller to get this task execution results, or may block the execution until the task is completed.

This problem has been in existence in JDK1.5 beginning of the design, development JDK1.8 introduced CompletableFuture before they get the perfect enhancement.

In the meantime, Google's open-source toolkit provides Guava ListenableFuture, to support callbacks, interested friends can access their own research support when the task is completed.

In the introduction to business demand scenario, different sections of the data sources are different, and that there is data dependency between the plate and the plate.

There can be understood as the timing relationships between tasks and task, according to some of the features offered CompletableFuture, is well suited to this business scenario.

CompletableFuture 类图:

CompletableFuture 类图

CompletableFuture realized the Future and CompletionStage two interfaces. Future interfaces to implement asynchronous tasks concerned about what the end, and get the results of asynchronous task execution. CompletionStage implement an interface, it provides a very rich feature set, and serial relationship, parallel relations, pooling relations.

CompletableFuture core strengths:

1) without manual maintenance thread, the thread assigned to tasks without requiring developers to focus on;

2) In use, the semantics more clear;

For example: t3 = t1.thenCombine (t2, () -> {// doSomething ...} can be clearly stated after the task 3 and task to wait for a completion of the task 2 will begin.

3) the code more concise, to support chained calls, allowing you to focus more on business logic.

4) easy handling exceptions

Next, to simulate multi-plate CompletableFuture data aggregation processing in the album.

Code as follows:

public static void main(String[] args) throws Exception {
    // 暂存数据
    List<String> stashList = Lists.newArrayList();
    // 任务 1:调用推荐接口获取数据
    CompletableFuture<String> t1 =
                    CompletableFuture.supplyAsync(() -> {
                            System.out.println("T1: 获取推荐接口数据...");
                            sleepSeconds(5);
                            stashList.add("[T1 板块数据]");
                            return "[T1 板块数据]";
                    });
    // 任务 2:调用搜索接口获取数据
    CompletableFuture<String> t2 =
                    CompletableFuture.supplyAsync(() -> {
                            System.out.println("T2: 调用搜索接口获取数据...");
                            sleepSeconds(3);
                            return " [T2 板块数据] ";
                    });
    // 任务 3:任务 1 和任务 2 完成后执行,聚合结果
    CompletableFuture<String> t3 =
                    t1.thenCombine(t2, (t1Result, t2Result) -> {
                            System.out.println(t1Result + " 与 " + t2Result + "实现去重逻辑处理");
                            return "[T1 和 T2 板块数据聚合结果]";
                    });
    // 等待任务 3 执行结果
    System.out.println(t3.get(6, TimeUnit.SECONDS));
}

static void sleepSeconds(int timeout) {
    try {
            TimeUnit.SECONDS.sleep(timeout);
    } catch (InterruptedException e) {
            e.printStackTrace();
    }
}

Execution results are as follows:

> Task :CompletableFutureTest.main()
T1: 获取推荐接口数据...
T2: 调用搜索接口获取数据...
[T1 板块数据] 与  [T2 板块数据] 实现去重逻辑处理
[T1 和 T2 板块数据聚合结果]

In the sample code above IDEA in a new Class, copied directly into, to run properly.

** 5, CompletableFuture production proposal **


Create a reasonable thread pool:

In a production environment, without directly using the recommended sample code. Because the sample code used in
CompletableFuture.supplyAsync(() -> {});
supplyAsync () method (used here in the factory method pattern) to create CompletableFuture objects, the underlying thread pool used by default, not be able to meet business needs.

Binding look underlying source:

// 默认使用 ForkJoinPool 线程池
private static final Executor asyncPool = useCommonPool ?
       ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
     return asyncSupplyStage(asyncPool, supplier);
}

Creating ForkJoinPool thread pool:
the default thread pool size is Runtime.getRuntime () availableProcessors () - 1 (CPU core number --1), you can set the thread pool size by -Djava.util.concurrent.ForkJoinPool.common.parallelism JVM parameters.

-Djava.util.concurrent.ForkJoinPool.common.threadFactory configuration settings based on the JVM thread factory parameters; -Djava.util.concurrent.ForkJoinPool.common.exceptionHandler configuration settings exception class of these two parameters set, internally through the system class loader loads class.

If all CompletableFuture use the default thread pool, once the task execution is slow I / O operations, it will lead all threads are blocked on I / O operation, thereby affecting the overall system performance.

Therefore, we recommend that you use in the production environment, 根据不同的业务类型创建不同的线程池,以避免互相影响.

CompletableFuture also provides another way to support the thread pool.

// 第二个参数支持传递 Executor 自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
                                                       Executor executor) {
        return asyncSupplyStage(screenExecutor(executor), supplier);
}

Custom thread pool, the proposed reference "Alibaba Java Development Manual", recommended ThreadPoolExecutor custom thread pool, use a bounded queue, the queue size is set according to actual business situations.

Thread pool size settings in the "Java Concurrency real" book, Brian Goetz provided a lot of optimization recommendations. If an excessive number of thread pool, competitive CPU and memory resources, resulting in a lot of time on context switching. Conversely, if the number of thread pool is too small, you can not make full use of the advantages of multi-core CPU.

The utilization ratio of CPU processor thread pool size can be estimated using the following equation:

Thread pool size is calculated

Exception Handling:

CompletableFuture provides a very simple exception handling, as these methods to support chained programming.

// 类似于 try{}catch{} 中的 catch{}
public CompletionStage<T> exceptionally
        (Function<Throwable, ? extends T> fn);
                
// 类似于 try{}finally{} 中的 finally{},不支持返回结果
public CompletionStage<T> whenComplete
        (BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync
        (BiConsumer<? super T, ? super Throwable> action);
                
// 类似于 try{}finally{} 中的 finally{},支持返回结果
public <U> CompletionStage<U> handle
        (BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync
        (BiFunction<? super T, Throwable, ? extends U> fn);

#### 6, CompletableFuture performance test:

Pressure measuring cycle as shown number of tasks, each time the pressure measured, the results from a superposition of convergence to jobNum data, time-consuming calculations.
Statistics dimensions: CompletableFuture default and custom thread pool thread pool.
Performance Test Code:

// 性能测试代码
Arrays.asList(-3, -1, 0, 1, 2, 4, 5, 10, 16, 17, 30, 50, 100, 150, 200, 300).forEach(offset -> {
                    int jobNum = PROCESSORS + offset;
                    System.out.println(
                                    String.format("When %s tasks => stream: %s, parallelStream: %s, future default: %s, future custom: %s",
                                                    testCompletableFutureDefaultExecutor(jobNum), testCompletableFutureCustomExecutor(jobNum)));
});

// CompletableFuture 使用默认 ForkJoinPool 线程池
private static long testCompletableFutureDefaultExecutor(int jobCount) {
    List<CompletableFuture<Integer>> tasks = new ArrayList<>();
    IntStream.rangeClosed(1, jobCount).forEach(value -> tasks.add(CompletableFuture.supplyAsync(CompleteableFuturePerfTest::getJob)));

    long start = System.currentTimeMillis();
    int sum = tasks.stream().map(CompletableFuture::join).mapToInt(Integer::intValue).sum();
    checkSum(sum, jobCount);
    return System.currentTimeMillis() - start;
}

// CompletableFuture 使用自定义的线程池
private static long testCompletableFutureCustomExecutor(int jobCount) {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(200, 200, 5, TimeUnit.MINUTES, new ArrayBlockingQueue<>(100000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setName("CUSTOM_DAEMON_COMPLETABLEFUTURE");
                    thread.setDaemon(true);
                    return thread;
            }
    }, new ThreadPoolExecutor.CallerRunsPolicy());

    List<CompletableFuture<Integer>> tasks = new ArrayList<>();
    IntStream.rangeClosed(1, jobCount).forEach(value -> tasks.add(CompletableFuture.supplyAsync(CompleteableFuturePerfTest::getJob, threadPoolExecutor)));

    long start = System.currentTimeMillis();
    int sum = tasks.stream().map(CompletableFuture::join).mapToInt(Integer::intValue).sum();
    checkSum(sum, jobCount);
    return System.currentTimeMillis() - start;
}

Test machine configuration: 8-core CPU, 16G memory

Performance test results:

Performance Test Results

According to the press to see the test results, along with the greater number of pressure measurement tasks, use the default thread pool performance worse.

7, CompletableFuture Extended:


Object creation:

In addition to the aforementioned methods supplyAsync, CompletableFuture also provides the following methods:

// 执行任务,CompletableFuture<Void> 无返回值,默认线程池
public static CompletableFuture<Void> runAsync(Runnable runnable) {
      return asyncRunStage(asyncPool, runnable);
}
// 执行任务,CompletableFuture<Void> 无返回值,支持自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable,
                                                   Executor executor) {
        return asyncRunStage(screenExecutor(executor), runnable);
}

We CompletableFuture combat mode, referred to the CompletableFuture realized CompletionStage interface, which provides a very rich functionality.

CompletionStage interface supports serial relations, pooling AND relations, pooling OR relationship.
The following interfaces for these relations to be a simple description, you can go to access JDK API itself when in use.
Meanwhile, each of these relationships interfaces provides a corresponding method xxxAsync () method for asynchronous execution of tasks.

Serial relationship:

CompletionStage description Serial relations, mainly thenApply, thenRun, thenAccept and thenCompose serial interface.

Source as follows:

// 对应 U apply(T t) ,接收参数 T并支持返回值 U
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn);

// 不接收参数也不支持返回值
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);

// 接收参数但不支持返回值
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);

// 组合两个依赖的 CompletableFuture 对象
public <U> CompletionStage<U> thenCompose
        (Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletionStage<U> thenComposeAsync
        (Function<? super T, ? extends CompletionStage<U>> fn);

Aggregation AND relationship:

CompletionStage describe convergence AND relationship, there are thenCombine, thenAcceptBoth and runAfterBoth serial interface.

Source below (not the Async method):

// 当前和另外的 CompletableFuture 都完成时,两个参数传递给 fn,fn 有返回值
public <U,V> CompletionStage<V> thenCombine
        (CompletionStage<? extends U> other,
         BiFunction<? super T,? super U,? extends V> fn);

// 当前和另外的 CompletableFuture 都完成时,两个参数传递给 action,action 没有返回值
public <U> CompletionStage<Void> thenAcceptBoth
        (CompletionStage<? extends U> other,
         BiConsumer<? super T, ? super U> action);

// 当前和另外的 CompletableFuture 都完成时,执行 action
public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,
                                              Runnable action);

Aggregation OR relationship:

CompletionStage describe convergence OR relationship, there are applyToEither, acceptEither and runAfterEither serial interface.

Source below (not the Async method):

// 当前与另外的 CompletableFuture 任何一个执行完成,将其传递给 fn,支持返回值
public <U> CompletionStage<U> applyToEither
        (CompletionStage<? extends T> other,
         Function<? super T, U> fn);

// 当前与另外的 CompletableFuture 任何一个执行完成,将其传递给 action,不支持返回值
public CompletionStage<Void> acceptEither
        (CompletionStage<? extends T> other,
         Consumer<? super T> action);

// 当前与另外的 CompletableFuture 任何一个执行完成,直接执行 action
public CompletionStage<Void> runAfterEither(CompletionStage<?> other,
                                                Runnable action);

This, CompletableFuture related features are introduced over.

Asynchronous programming gradually become more and more mature, Java language's official website also begun to support asynchronous programming model, so learn asynchronous programming is necessary.

Combining business needs driven scenario, leads to the Future design patterns combat, then the JDK1.8 CompletableFuture is how to use, core strengths, performance comparison test, using extensions to do further analysis.

I hope to be helpful!

I welcome the attention of the public number, scan two-dimensional code to unlock attention more exciting articles, grow with you ~
Java enthusiasts community

Guess you like

Origin www.cnblogs.com/ldws/p/11627139.html
Recommended