Usage and difference between Java Future and completable future

Posted by saleemshehzad on Sat, 18 Dec 2021 16:31:54 +0100

Future usage
In a multithreaded scenario, the runnable interface is generally implemented, the run method is overridden, and the return value is of void type. Therefore, the return result of the thread is not required in this case.  
If you need the return result of the thread, you need to use the callable interface instead.  
The usage of callable is the same as that of runnable, except that it overrides the call method, which has a generic return value type, which can be specified as needed.  
So when will it arrive in the Future? When you start the callable thread, you can declare a Future object to receive the returned results.
Futrue can monitor the call of the target thread. When you call the get() method of Future to get the result, the caller's thread will be blocked until the call method of the target thread ends and returns the result.
The Future interface is generally used to retrieve the status of Callable execution. The main methods are as follows:

Cancel to cancel the execution of the Callable. When the Callable has not been completed
Get to get the return value of Callable
isCanceled to determine whether it is cancelled
isDone, judge whether it is completed
Take a chestnut.
Four just need (threads) to buy a house and get the lottery results in the future. The lottery results are blocked until they come out.

public class FutureTest {
 
    /**
* buy a house and wave numbers
     */
    public static class Yaohao implements Callable<Integer> {
        /**
* return lottery results
* @ return 0: Winning 1: not winning
         * @throws Exception
         */
        @Override
        public Integer call() throws Exception {
            Random random = new Random();
/ / simulate the lottery, and the results will be displayed within 10 days
            TimeUnit.SECONDS.sleep(random.nextInt(10));
            int result = random.nextInt(2);
            System.out.println("     "+Thread.currentThread().getName()+" is done!");
            return result;
        }
    }
 
    public static void main(String[] args) throws InterruptedException, ExecutionException {
 
        Yaohao gangxu1 = new Yaohao();
        Yaohao gangxu2 = new Yaohao();
        ExecutorService es = Executors.newCachedThreadPool();
        Future<Integer> result1 = es.submit(gangxu1);
        Future<Integer> result2 = es.submit(gangxu2);
        es.shutdown();
 
        System.out.println("just need 1, Yaohao result:" + (result1.get()==1? "Winning": "not winning");
        System.out.println("just need 2, lottery result:" + (result2.get()==1? "Winning": "not winning");
    }
 
}
Usage of completable future
Create asynchronous operations, runAsync (return value is not supported) and supplyAsync methods (return value is supported)
Callback method when the calculation result is completed
whenComplete: the thread that has finished executing the current task continues to execute the whenComplete task.
whenCompleteAsync: the thread that finishes executing the current task and continues to submit the task of whenCompleteAsync to the thread pool for execution.
Exceptionally: when an exception occurs in the current task, execute the callback method in exceptionally.
thenApply method. When a thread depends on another thread, you can use thenApply method to serialize the two threads.
handle method
handle is the processing of the result when the execution task is completed.
The handle method is handled in much the same way as the thenApply method. The difference is that the handle is executed after the task is completed. It can also handle abnormal tasks. thenApply can only execute normal tasks. If the task is abnormal, thenApply method will not be executed.
thenAccept consumes the processing result, receives the processing result of the task, and consumes the processing result. No result is returned.
Unlike the thenAccept method, the thenRun method does not care about the processing result of the task. As long as the above task is completed, thenAccept will be executed.
thenCombine merges tasks. thenCombine will execute both completion stage tasks, and then hand over the results of the two tasks to thenCombine for processing.
Thenpose method. Thenpose method allows you to pipeline two completionstages. When the first operation is completed, the result is passed to the second operation as a parameter.
Case:

1. runAsync and supplyAsync methods

Completable future provides four static methods to create an asynchronous operation.

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

Methods that do not specify an Executor will use forkjoinpool Commonpool () executes asynchronous code as its thread pool. If a thread pool is specified, it runs with the specified thread pool. All of the following methods are similar.

  • The runAsync method does not support return values.
  • supplyAsync can support return values.

Example

//No return value
public static void runAsync() throws Exception {
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        System.out.println("run end ...");
    });
    
    future.get();
}

//There is a return value
public static void supplyAsync() throws Exception {         
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        System.out.println("run end ...");
        return System.currentTimeMillis();
    });

    long time = future.get();
    System.out.println("time = "+time);
}

2. Callback method when the calculation result is completed

When the calculation result of completable future is completed or an exception is thrown, a specific Action can be executed. The main methods are as follows:

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

You can see that the type of Action is biconsumer <? super T,? Super throwable > it can handle normal calculation results or abnormal conditions.

The difference between whenComplete and whenCompleteAsync:
whenComplete: the thread executing the current task executes the task that continues to execute whenComplete.
whenCompleteAsync: submit whenCompleteAsync to the thread pool for execution.

Example

public static void whenComplete() throws Exception {
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        if(new Random().nextInt()%2>=0) {
            int i = 12/0;
        }
        System.out.println("run end ...");
    });
    
    future.whenComplete(new BiConsumer<Void, Throwable>() {
        @Override
        public void accept(Void t, Throwable action) {
            System.out.println("Execution complete!");
        }
        
    });
    future.exceptionally(new Function<Throwable, Void>() {
        @Override
        public Void apply(Throwable t) {
            System.out.println("Execution failed!"+t.getMessage());
            return null;
        }
    });
    
    TimeUnit.SECONDS.sleep(2);
}

3. thenApply method

When a thread depends on another thread, the thenApply method can be used to serialize the two threads.

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

Function<? super T,? extends U>
T: The type of result returned by the previous task
U: Return value type of the current task

Example

private static void thenApply() throws Exception {
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>() {
        @Override
        public Long get() {
            long result = new Random().nextInt(100);
            System.out.println("result1="+result);
            return result;
        }
    }).thenApply(new Function<Long, Long>() {
        @Override
        public Long apply(Long t) {
            long result = t*5;
            System.out.println("result2="+result);
            return result;
        }
    });
    
    long result = future.get();
    System.out.println(result);
}

The second task depends on the results of the first task.

4. handle method

handle is the processing of the result when the execution task is completed.
The handle method is handled in much the same way as the thenApply method. The difference is that the handle is executed after the task is completed. It can also handle abnormal tasks. thenApply can only execute normal tasks. If the task is abnormal, thenApply method will not be executed.

public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

Example

public static void handle() throws Exception{
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {

        @Override
        public Integer get() {
            int i= 10/0;
            return new Random().nextInt(10);
        }
    }).handle(new BiFunction<Integer, Throwable, Integer>() {
        @Override
        public Integer apply(Integer param, Throwable throwable) {
            int result = -1;
            if(throwable==null){
                result = param * 2;
            }else{
                System.out.println(throwable.getMessage());
            }
            return result;
        }
     });
    System.out.println(future.get());
}

It can be seen from the example that in the handle, the corresponding subsequent processing operations can be performed according to whether there are exceptions in the task. In the thenApply method, if there is an error in the previous task, the thenApply method will not be executed.

5. thenAccept consumption processing result

Receive the processing result of the task and consume it. No result is returned.

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

Example

public static void thenAccept() throws Exception{
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            return new Random().nextInt(10);
        }
    }).thenAccept(integer -> {
        System.out.println(integer);
    });
    future.get();
}

As can be seen from the example code, this method only consumes the completed task and can be processed according to the results returned by the above task. There is no subsequent input error operation.

6. thenRun method

Unlike the thenAccept method, it does not care about the processing results of the task. As long as the above task is completed, thenAccept will be executed.

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

Example

public static void thenRun() throws Exception{
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            return new Random().nextInt(10);
        }
    }).thenRun(() -> {
        System.out.println("thenRun ...");
    });
    future.get();
}

This method is similar to thenAccept method. The difference is that after the last task is processed, the calculation result will not be passed to thenRun method. After processing the play task, execute the subsequent operations of thenAccept.

7. thenCombine merge task

thenCombine will execute both CompletionStage tasks, and then hand over the results of the two tasks to thenCombine for processing.

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);

Example

private static void thenCombine() throws Exception {
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            return "hello";
        }
    });
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            return "hello";
        }
    });
    CompletableFuture<String> result = future1.thenCombine(future2, new BiFunction<String, String, String>() {
        @Override
        public String apply(String t, String u) {
            return t+" "+u;
        }
    });
    System.out.println(result.get());
}

8,thenAcceptBoth

When both completionstages are completed, the results are handed over to thenAcceptBoth for consumption

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action,     Executor executor);

Example

private static void thenAcceptBoth() throws Exception {
    CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f1="+t);
            return t;
        }
    });
        
    CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f2="+t);
            return t;
        }
    });
    f1.thenAcceptBoth(f2, new BiConsumer<Integer, Integer>() {
        @Override
        public void accept(Integer t, Integer u) {
            System.out.println("f1="+t+";f2="+u+";");
        }
    });
}

9. applyToEither method

Two completionstages. Whoever executes the returned result quickly, I will use the result of that CompletionStage for the next conversion operation.

public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn,Executor executor);

Example

private static void applyToEither() throws Exception {
    CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f1="+t);
            return t;
        }
    });
    CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f2="+t);
            return t;
        }
    });
    
    CompletableFuture<Integer> result = f1.applyToEither(f2, new Function<Integer, Integer>() {
        @Override
        public Integer apply(Integer t) {
            System.out.println(t);
            return t * 2;
        }
    });

    System.out.println(result.get());
}

10. acceptEither method

Two completionstages. Whoever executes the returned result quickly, I will use the result of that CompletionStage for the next consumption operation.

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action,Executor executor);

Example

private static void acceptEither() throws Exception {
    CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f1="+t);
            return t;
        }
    });
        
    CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f2="+t);
            return t;
        }
    });
    f1.acceptEither(f2, new Consumer<Integer>() {
        @Override
        public void accept(Integer t) {
            System.out.println(t);
        }
    });
}

11. runAfterEither method

There are two completionstages. When either is completed, the next operation (Runnable) will be executed

public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);

Example

private static void runAfterEither() throws Exception {
    CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f1="+t);
            return t;
        }
    });
        
    CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f2="+t);
            return t;
        }
    });
    f1.runAfterEither(f2, new Runnable() {
        
        @Override
        public void run() {
            System.out.println("One of the above has been completed.");
        }
    });
}

12,runAfterBoth

For the two completionstages, the next operation (Runnable) will not be executed until the calculation is completed

public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);

Example

private static void runAfterBoth() throws Exception {
    CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f1="+t);
            return t;
        }
    });
        
    CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f2="+t);
            return t;
        }
    });
    f1.runAfterBoth(f2, new Runnable() {
        
        @Override
        public void run() {
            System.out.println("The above two tasks have been completed.");
        }
    });
}

13. Thenpose method

Thenpose method allows you to pipeline two completionstages. When the first operation is completed, the result is passed to the second operation as a parameter.

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

Example

private static void thenCompose() throws Exception {
        CompletableFuture<Integer> f = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("t1="+t);
                return t;
            }
        }).thenCompose(new Function<Integer, CompletionStage<Integer>>() {
            @Override
            public CompletionStage<Integer> apply(Integer param) {
                return CompletableFuture.supplyAsync(new Supplier<Integer>() {
                    @Override
                    public Integer get() {
                        int t = param *2;
                        System.out.println("t2="+t);
                        return t;
                    }
                });
            }
            
        });
        System.out.println("thenCompose result : "+f.get());
    }

The difference between Future and completable Future
Future was introduced in Java 5.

Advantages: to some extent, it allows tasks in a thread pool to execute asynchronously
Disadvantages: the biggest problem with traditional callbacks is that they can't separate the control flow into different event handlers. For example, if the main thread waits for the result returned by each asynchronously executed thread to do the next operation, it must be blocked in future Get () waits for the result to return, which becomes synchronization.

Completable future was introduced in Java 8.

The Future and CompletionStage interfaces are implemented, which retains the advantages of Future and makes up for its shortcomings. That is, after an asynchronous task is completed, you do not need to wait when you need to continue the operation with its results. The results of the previous asynchronous processing can be directly handed over to another asynchronous event processing thread through thenAccept, thenApply, thenCompose, etc.
It can be seen that this method is the asynchronous processing we need. Multiple asynchronous event processing of a control flow can be seamlessly connected together.
 

Topics: Java Multithreading