Java programming logic (94) - the combined Asynchronous Programming

Front two data processing functions is discussed in 8 Java, it is an enhancement type container section 38 to section 55 described, it will be a set of a plurality of operation data together in a pipelined manner. This section continues to discuss the new features in Java 8, is a major new class CompletableFuture, it is an enhancement of section 65 to section 82 concurrent programming introduced, it can easily have multiple asynchronous tasks certain dependencies in a pipelined together, greatly simplify the development of multi asynchronous tasks.

Before introducing the contents of so many concurrent programming, what problem can not be solved? CompletableFuture in the end be able to solve the problem? Before introducing the content and what is the relationship? How you use? What is the basic principle? This section is discussed in detail, let's look at it to solve the problem.

Asynchronous Task Management

In modern software development, system more complex functions, complex management of the method is to divide and rule, many functions of the system may be cut into smaller services, provide external Web API, a separate development, deployment and maintenance. For example, in an electricity supplier system, there may be special products and services, service orders, customer service and referral service, preferential services, search services, etc., when the external display a specific page, you may want to call multiple services, and more there may be some dependencies between calls, for example, to display a product page, you need to call products and services, call the referral service may need to obtain additional recommendation related to the products, but also may need to call special services to obtain the product-related promotional offers and in order to invoke special services, you may need to call customer service to get the user's membership level.

In addition, many modern software often rely on third-party services, such as map services, messaging services, weather services, exchange services, when implementing a specific feature, you may want to visit more of these services may exist between some of these visits dependencies.

To improve performance, make full use of system resources, these calls to external services in general should be asynchronous, concurrent as possible. We introduced asynchronous task execution services in section 77, may be submitted easily using ExecutorService single independent asynchronous tasks, they can easily access the results of asynchronous tasks by Future interfaces when needed, but more particularly for a certain dependencies asynchronous tasks, this support is not enough.

Thus, there CompletableFuture, which is a concrete class that implements two interfaces, one Future, another is CompletionStage, Future shows the result of asynchronous tasks, while completion stage CompletionStage literally, a plurality of lines may be CompletionStage way combined for one CompletionStage, it has a computing tasks, but may need to wait for the other to complete one or more stages can begin, after it is completed, may trigger other stages start running. CompletionStage provides a number of methods, use them, you can easily respond to an event task, the task to build a pipeline to achieve modular asynchronous programming.

Specifically how to use it? Now we're going to walk, CompletableFuture is a Future, let's look at the Future similar places.

And Future / FutureTask Comparative

The basic task execution service

Let's take a brief review by example asynchronous task execution service and Future, asynchronous task execution services, with a Callable or Runnable represents the task to Callable for example, a simulated external tasks:

Copy the code
private static Random rnd = new Random();

static int delayRandom(int min, int max) {
    int milli = max > min ? rnd.nextInt(max - min) : 0;
    try {
        Thread.sleep(min + milli);
    } catch (InterruptedException e) {
    }
    return milli;
}

static Callable<Integer> externalTask = () -> {
    int time = delayRandom(20, 2000);
    return time;
};
Copy the code

externalTask ​​represent external tasks, we used Lambda expressions are not familiar with can see Section 91, delayRandom for analog delay.

Assuming an asynchronous task execution services, its code is:

private static ExecutorService executor =
        Executors.newFixedThreadPool(10);

Call service tasks performed by external services, general return Future, represents the asynchronous result, the sample code is:

public static Future<Integer> callExternalService(){
    return executor.submit(externalTask);
}

In the main program, combined with asynchronous tasks and sample code for local calls:

Copy the code
static void Master public () { 
    // perform asynchronous tasks 
    Future <Integer> = asyncRet callExternalService (); 

    // perform other tasks ... 

    // get asynchronous tasks result, exception handling may be 
    the try { 
        Integer RET = asyncRet.get (); 
        System.out.println (RET); 
    } the catch (InterruptedException E) { 
        e.printStackTrace (); 
    } the catch (ExecutionException E) { 
        e.printStackTrace (); 
    } 
}
Copy the code

Basic CompletableFuture

Use CompletableFuture can achieve similar functionality, but it does not support the use of Callable represent asynchronous tasks, and support Runnable and Supplier, Supplier alternative Callable expressed return the results of asynchronous tasks, and Callale difference is that it does not throw an exception subjects, if exception occurs, you can throw a runtime exception.

Supplier expressed using asynchronous tasks, code Callable Similarly, to replace the variable types, namely:

static Supplier<Integer> externalTask = () -> {
    int time = delayRandom(20, 2000);
    return time;
};

Use Code CompletableFuture call external services can be:

public static Future<Integer> callExternalService(){
    return CompletableFuture.supplyAsync(externalTask, executor);
}

supplyAsync is a static method, which is defined as:

public static <U> CompletableFuture<U> supplyAsync(
    Supplier<U> supplier, Executor executor)

It accepts two parameters supplier and executor, internal, it uses the executor to perform tasks supplier represented a return CompletableFuture, after the call, the task is executed asynchronously, this method returns immediately.

supplyAsync there is a method without parameters executor:

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)

There is no executor, whom the task enforce it? Configuration and system environment and, in general, if the number of available CPU core is greater than 2, will be introduced by the use of Fork Java 7 / Join task execution services, that ForkJoinPool.commonPool (), the number of worker threads behind the task execution services in general minus 1, that Runtime.getRuntime () is the number of CPU cores availableProcessors () -. 1, otherwise, would use ThreadPerTaskExecutor, it creates a thread for each task.

For CPU-intensive computing tasks, use the Fork / Join is appropriate task execution service, but for the average call external services asynchronous tasks, Fork / Join might be inappropriate because of its relatively low degree of parallelism, may make multitasking serial could run concurrently, then, it should provide Executor parameters.

Later we will see a lot of ways to end Async named, generally have two versions, one with Executor parameters and one without, the meaning is the same, will not repeat described.

For the method of type Runnable task is to build CompletableFuture:

public static CompletableFuture<Void> runAsync(
    Runnable runnable)
public static CompletableFuture<Void> runAsync(
    Runnable runnable, Executor executor)

It is similar with supplyAsync, specifically not go into details.

Future enhancements to the basic CompletableFuture

Future Some interfaces, CompletableFuture are supported, however, CompletableFuture There are additional related methods, such as:

public T join()
public boolean isCompletedExceptionally()
public T getNow(T valueIfAbsent)

join and get a similar method will wait for the end of the task, but it does not throw an exception subjects, an exception if the task is over, will join abnormal abnormal CompletionException packaging is thrown at runtime.

Future has isDone method to check whether the task is over, but do not know the task is a normal or abnormal end, isCompletedExceptionally method can determine whether the task is abnormal over.

getNow and join similar, except that, if the task is not over, it will not wait, but will return parameters passed valueIfAbsent.

Further understanding Future / CompletableFuture

In the previous example are used in task execution services, in fact, asynchronous task execution services and results Future is not tied together, you can create your own thread returns asynchronous result, in order to further understanding, we look at some examples.

Use FutureTask calls an external service, the code can be:

Copy the code
public static Future<Integer> callExternalService() {
    FutureTask<Integer> future = new FutureTask<>(externalTask);
    new Thread() {
        public void run() {
            future.run();
        }
    }.start();
    return future;
}
Copy the code

Within their own created a thread, the thread calls the run method FutureTask, we analyzed FutureTask code in Section 77, run method calls the method externalTask ​​the call, and save the results or encounter an exception, wake up a thread waiting for the results.

Use CompletableFuture, you can also create a direct thread and return the asynchronous result, the code can be:

Copy the code
public static Future<Integer> callExternalService() {
    CompletableFuture<Integer> future = new CompletableFuture<>();
    new Thread() {
        public void run() {
            try {
                future.complete(externalTask.get());
            } catch (Exception e) {
                future.completeExceptionally(e);
            }
        }
    }.start();
    return future;
}
Copy the code

This uses two methods CompletableFuture of:

public boolean complete(T value)
public boolean completeExceptionally(Throwable ex) 

These two methods explicitly set the task status and results, the task completes successfully Complete set, a result value, completeExceptionally provided abend, abnormal ex. Future interface no corresponding method, FutureTask relevant public methods but not (YES protected). After setting, they will trigger other dependent on their CompletionStage. What specific triggers it? We next look.

The results or abnormal response

Use Future, we can only get through get results, get might need to block waiting for, and by CompletionStage, you can register a callback function, when the task is completed or abnormally automatically trigger the execution, there are two types of registration methods, whenComplete and handle, we Taken separately below.

whenComplete

whenComplete statement is:

public CompletableFuture<T> whenComplete(
    BiConsumer<? super T, ? super Throwable> action)

Parameters action represents the callback function, regardless of the previous stage is normal or abnormal end, it will be called, is a function of the type of BiConsumer, accepts two parameters, the first parameter is the result of the normal value at the end of the second parameter is abnormal abnormal, BiConsumer no return value at the end. whenComplete return value or CompletableFuture, it will not change the outcome of the original stage, can continue to call other functions on it. Look at a simple example:

Copy the code
CompletableFuture.supplyAsync(externalTask).whenComplete((result, ex) -> {
    if (result != null) {
        System.out.println(result);
    }
    if (ex != null) {
        ex.printStackTrace();
    }
}).join();
Copy the code

result shows the results of the previous phase, ex indicates an abnormal, may only have a not null.

Registered specific function whenComplete who enforce it? In general, it depends on when the registration status of the task, if the task is not over yet registered, the registration function will be executed by the thread executing tasks, perform the registration function after the thread executing the task, if the task has been registered over, by the current thread (ie, the calling thread registration function) execution.

If you do not want the current thread of execution, to avoid possible synchronous blocking, you can use the other two asynchronous register:

public CompletableFuture<T> whenCompleteAsync(
    BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(
    BiConsumer<? super T, ? super Throwable> action, Executor executor)       

And Async method described earlier to the end of the same, registration will function action (or ThreadPerTaskExecutor execution that is ForkJoinPool.commonPool ()) method of the first tasks performed by the default service, for the second method will be specified by the parameters the executor execution.

handle

whenComplete just registered callback function does not change the outcome, it returns a CompletableFuture, but the results of this CompletableFuture and call it CompletableFuture is the same, there is a similar registration method handle, which declared:

public <U> CompletableFuture<U> handle(
    BiFunction<? super T, Throwable, ? extends U> fn)

BiFunction is a callback function, also accepts two parameters, a result is normal, and the other is abnormal, but BiFunction return value, in CompletableFuture handle the returned result is the return value BiFunction Alternatively, even if the original abnormal, and It will be covered, such as:

Copy the code
String ret =
    CompletableFuture.supplyAsync(()->{
        throw new RuntimeException("test");
    }).handle((result, ex)->{
        return "hello";
    }).join();
System.out.println(ret);
Copy the code

The output is "hello". Asynchronous tasks throws an exception, but by the handle method to change the results.

And whenComplete similar, handle also has a corresponding asynchronous registration method handleAsync, we will not discuss the specific.

exceptionally

whenComplete and handle are both normal response to complete response is also an exception, if only interested in exceptions, can be used exceptionally, it declared:

public CompletableFuture<T> exceptionally(
    Function<Throwable, ? extends T> fn)

It is registered callback function Function, parameter received is abnormal, a return value, similar to the handle, it will change the results, the example is not specifically.

In addition to response and abnormal results, use CompletableFuture, you can easily build a variety of task dependencies flow, let's look at a simple case of a single-stage-dependent.

Construction of relying on a single stage of the task flow

thenRun

After a normal stage is completed, the next task, look at a simple example:

Copy the code
Runnable taskA = () -> System.out.println("task A");
Runnable taskB = () -> System.out.println("task B");
Runnable taskC = () -> System.out.println("task C");

CompletableFuture.runAsync(taskA)
    .thenRun(taskB)
    .thenRun(taskC)
    .join();
Copy the code

Here, there are three asynchronous tasks taskA, taskB TaskC and, naturally thenRun described by their dependencies, thenRun synchronous version, a corresponding asynchronous version thenRunAsync:

public CompletableFuture<Void> thenRunAsync(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor)

In thenRun built task flow, not only the first phase ends abnormally, the next phase of the mission will be implemented, if the previous stage exception occurs, all subsequent phases will not run, the result will be set to the same exception, call to join will throw an exception when CompletionException run.

The next task is to specify the Runnable thenRun type, it does not require the result of a previous stage as parameters and returns no value, so that, in CompletableFuture thenRun returned, as a result of Void type, i.e. no results.

thenAccept/thenApply

If the result of the next task requires the previous stage as a parameter, you can use thenAccept or thenApply method:

public CompletableFuture<Void> thenAccept(
    Consumer<? super T> action)
public <U> CompletableFuture<U> thenApply(
    Function<? super T,? extends U> fn)

Consumer thenAccept task type, results of the previous stage which accepts as a parameter, there is no return value. thenApply task type is Function, accept the results of the previous stage as a parameter and returns a new value, this value will be the result of the value thenApply returned CompletableFuture. Look at a simple example:

Copy the code
Supplier<String> taskA = () -> "hello";
Function<String, String> taskB = (t) -> t.toUpperCase();
Consumer<String> taskC = (t) -> System.out.println("consume: " + t);

CompletableFuture.supplyAsync(taskA)
    .thenApply(taskB)
    .thenAccept(taskC)
    .join();
Copy the code

TaskA result is "hello", passed to taskB, taskB conversion results "HELLO", then the result to taskC, taskC been output, the output is:

consume: HELLO

There are many names with CompletableFuture run, accept or apply the method, which generally corresponds to the task type, corresponding to a Runnable RUN, corresponding to a Consumer Accept, and apply the corresponding Function, follow not repeat.

thenCompose

And thenApply similar, there is a method thenCompose, declared:

public <U> CompletableFuture<U> thenCompose(
    Function<? super T, ? extends CompletionStage<U>> fn)

This type of task is Function, also accept the results of the previous phase, returns a new result, but this transfer function fn return type is CompletionStage, that is, its return value is a stage, if thenApply, results will become CompletableFuture <CompletableFuture <U >>, used thenCompose, returns distinction CompletionStage, thenCompose fn of the thenApply returned directly, just as the difference in flatMap Stream API and the map to see a simple example:

Copy the code
Supplier<String> taskA = () -> "hello";
Function<String, CompletableFuture<String>> taskB = (t) ->
    CompletableFuture.supplyAsync(() -> t.toUpperCase());
Consumer<String> taskC = (t) -> System.out.println("consume: " + t);

CompletableFuture.supplyAsync(taskA)
    .thenCompose(taskB)
    .thenAccept(taskC)
    .join();
Copy the code

The above code, taskB is a conversion function, but also its own asynchronous task execution, return type is CompletableFuture, so use the thenCompose.

Construction of a two-stage-dependent task flow

We are dependent on the completion of two

thenRun, thenAccept, thenApply and thenCompose used to perform another task at a later stage is completed, there are some ways CompletableFuture used to perform another task in the latter two phases are completed, the method is:

Copy the code
public CompletableFuture<Void> runAfterBoth(
    CompletionStage<?> other, Runnable action
public <U,V> CompletableFuture<V> thenCombine(
    CompletionStage<? extends U> other,
    BiFunction<? super T,? super U,? extends V> fn)
public <U> CompletableFuture<Void> thenAcceptBoth(
    CompletionStage<? extends U> other,
    BiConsumer<? super T, ? super U> action) 
Copy the code

RunAfterBoth task type corresponding to the Runnable, thenCombine corresponding task type BiFunction, accept the results of the first two stages as a parameter and returns a result, thenAcceptBoth corresponding task type BiConsumer, accept the results of the first two stages as parameters, but not return result. They are asynchronous and the corresponding version with Executor parameter, used to specify the next task by whom, particularly not go into details. The current stage and another stage other parameters specified no dependencies, concurrent execution, when the end of the two perform, to perform the specified start another task.

Take a simple example, A and B after the end of the task execution tasks combined result C, the code is:

Copy the code
Supplier<String> taskA = () -> "taskA";
CompletableFuture<String> taskB = CompletableFuture.supplyAsync(() -> "taskB");
BiFunction<String, String, String> taskC = (a, b) -> a + "," + b;

String ret = CompletableFuture.supplyAsync(taskA)
        .thenCombineAsync(taskB, taskC)
        .join();
System.out.println(ret);
Copy the code

The output is:

Task, Taskbar

It relies on a two stage

The foregoing method requires two stages are completed before the next task, if only either one of phase is complete, the following method may be used:

Copy the code
public CompletableFuture<Void> runAfterEither(
    CompletionStage<?> other, Runnable action)

public <U> CompletableFuture<U> applyToEither(
    CompletionStage<? extends T> other, Function<? super T, U> fn)

public CompletableFuture<Void> acceptEither(
    CompletionStage<? extends T> other, Consumer<? super T> action)          
Copy the code

They are asynchronous and the corresponding version with Executor parameter, used to specify the next task by whom, particularly not go into details. The current stage and another stage other parameters specified no dependencies, concurrent execution, whenever one of the execution is over, it will start another task parameters specified, concrete will not go into details.

Build dependencies multiple stages of task flow

If more than two phase-dependent, can use the following method:

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

They are static methods, based on multiple CompletableFuture build a new CompletableFuture.

For allOf, when all the sub CompletableFuture are completed, it was complete, if any abnormal CompletableFuture ended, the new CompletableFuture result is unusual, however, because it does not have an exception to a premature end, but will wait for all phase ends, if there are multiple stages of abnormal end, save the new CompletableFuture exception is the last one. The new CompletableFuture will hold abnormal results, but does not save the normal end result, if required, can be obtained from each stage. Look at a simple example:

Copy the code
CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> {
    delayRandom(100, 1000);
    return "helloA";
}, executor);

CompletableFuture<Void> taskB = CompletableFuture.runAsync(() -> {
    delayRandom(2000, 3000);
}, executor);

CompletableFuture<Void> taskC = CompletableFuture.runAsync(() -> {
    delayRandom(30, 100);
    throw new RuntimeException("task C exception");
}, executor);

CompletableFuture.allOf(taskA, taskB, taskC).whenComplete((result, ex) -> {
    if (ex != null) {
        System.out.println(ex.getMessage());
    }
    if (!taskA.isCompletedExceptionally()) {
        System.out.println("task A " + taskA.join());
    }
});
Copy the code

taskC will first abnormal end, but will wait for the newly constructed CompletableFuture two other end, after the end of all, the status and results can be checked by sub-phases, sub-phase method (e.g. TaskA) a.

For CompletableFuture anyOf returned, when the first sub CompletableFuture or abnormally completed, it is finished or abnormal end respectively, the same results as the first sub CompletableFuture end, a specific example is not.

summary

This section describes the Java 8 in modular asynchronous programming CompletableFuture:

  • It is Future enhancements, but can respond to abnormal events or results, there are many ways to build asynchronous task flow
  • Who perform the task, generally three types of the corresponding method, the method name Async performed without a previous phase by the current thread or threads, but do not specify a method with Async Executor executed by default Excecutor (ForkJoinPool.commonPool () or ThreadPerTaskExecutor) a method designated with Async Executor and parameters specified by the execution Executor
  • According to the type of task, there are generally three types of the corresponding method, with a name corresponding to the Runnable run, with corresponding Consumer accept, apply with corresponding Function

Use CompletableFuture, can be simple and natural to express dependencies and execution processes between multiple asynchronous tasks, greatly simplify the code and improve readability.

The next section, we discuss Java 8 date and time to enhance the API.

Guess you like

Origin www.cnblogs.com/ivy-xu/p/12375393.html