Customized encapsulation of asynchronous task components to implement FutureTask function

  • FutureTask

CompletableFuture in the asynchronous orchestration API after JDK1.8 provides success callbacks and exception callbacks for asynchronous tasks.

public class FutureTaskTest {
    
    

    public static void main(String[] args) throws Exception {
    
    

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    
    
            try {
    
    
                Thread.sleep(3000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            return UUID.randomUUID().toString();
        });

        future.thenAccept((s) -> {
    
    
            System.out.println("异步任务的返回值" + s);
        });

        future.exceptionally((ex) -> {
    
    
            ex.printStackTrace();
            return null;
        });


        Thread.sleep(5000);

    }

}

After starting an asynchronous task, it is equivalent to starting a thread and setting a callback method for the asynchronous object. Then when the asynchronous task ends or an exception occurs, the relevant callback will be automatically called.

  • Customize AsyncTask to implement asynchronous task callback
public class AsyncTest {
    
    

    public static void main(String[] args) throws Exception {
    
    

        AsyncTask<String> asyncTask = new AsyncTask<>(new Callable<String>() {
    
    
            @Override
            public String call() throws Exception {
    
    
                System.out.println("开始处理数据");
                Thread.sleep(3000);
                String s = UUID.randomUUID().toString();
                System.out.println("数据计算完成");
                return s;
            }
        });

        asyncTask.run();

        Thread.sleep(5000);

        asyncTask.thenError((ex) -> {
    
    
            ex.printStackTrace();
        });

        asyncTask.thenSuccess((s) -> {
    
    
            System.out.println("任务数据:" + s);
        });

        System.out.println("主线程结束");

    }

}

class AsyncTask<T>{
    
    

    private Callable<T> task;

    private T value;

    private Exception e;

    private Consumer<T> successConsumer;

    private Consumer<? super Exception> exConsumer;

    private Thread thread;

    public AsyncTask(Callable<T> task){
    
    
        this.task = task;
    }

    public void run(){
    
    
        this.thread = new Thread(() -> {
    
    
            try {
    
    
                value = task.call();
                if (successConsumer != null){
    
    
                    successConsumer.accept(value);
                }
            } catch (Exception e) {
    
    
                this.e = e;
                if (exConsumer != null){
    
    
                    exConsumer.accept(e);
                }
            }
        });
        this.thread.start();
    }

    public void thenSuccess(Consumer<T> consumer){
    
    
        this.successConsumer = consumer;
        if (this.thread != null && !this.thread.isAlive() && e == null){
    
    
            consumer.accept(value);
        }
    }

    public void thenError(Consumer<? super Exception> consumer){
    
    
        this.exConsumer = consumer;
        if (this.thread != null && !this.thread.isAlive() && e != null){
    
    
            consumer.accept(e);
        }
    }

}

The asynchronous task callback is to execute the callback method set by other threads after the thread is finished running.
Here, the callback method of the asynchronous thread is set through thenSuccess, and then the following code is used to determine whether the asynchronous callback has been set.

 if (successConsumer != null){
    
    
   successConsumer.accept(value);
 }

There is another problem here. The main thread first calls the run method and then the thenSuccess method. If the execution time of the run method and thenSuccess method exceeds the execution time of the asynchronous task.

Insert image description here

There are 2 solutions to avoid this problem.

  1. Before running, call thenError and thenSuccess. After setting the callback, call run again.
    Since the callback has been set before starting the thread, there is no need to worry.

  2. Determine the status of the thread in the thenError and thenSuccess methods. If the isAlive method of the thread returns false, it means that the thread has finished running. The status of the running thread is true. Therefore, save the return value and exception of the thread and call thenError , thenSuccess method, it is judged that if the thread is completed (the status of the thread that called the start method will be true), this means that the callback has not been executed yet, then just call the callback method after getting the return value.

 if (this.thread != null && !this.thread.isAlive() && e == null){
    
    
            consumer.accept(value);
        }
  • Generics

Regarding the understanding of generics, a generic class can be imagined as a container. A container is a container that can hold a variety of things. This is true for every container. However, in order to facilitate the convenience of storing things that can be found quickly later, it is given to each container. Put a label on each cell, for example, the first cell holds only keys, the second cell only holds cups, etc. In this way, when you take it out from the cell, you will only get what you put in it. Generics are equivalent to a label on the object, identifying the things that the object is equipped with.

An object is a Java object with generics. During the process of passing it as a parameter, the label on the object will also be passed. The Java compiler will automatically check the type returned by the object with generics and the type passed in as a parameter. Object type, for example, the map method of stream will automatically infer the return value type when using lambda to return. If the accepted types do not match, the compiler will report an error. For example, when using lambda expression to define input parameters, there is no need to specify the parameter type. If it is generic If the type is String, then the methods of other classes cannot be adjusted. This is also guaranteed by the compiler. This is the automatic inference and transfer of generics.

Some instance methods require parameters that are also generic, such as those in AsyncTask.

    public void thenSuccess(Consumer<T> consumer){
    
    
        this.successConsumer = consumer;
        if (this.thread != null && !this.thread.isAlive() && e == null){
    
    
            consumer.accept(value);
        }
    }

    public void thenError(Consumer<? super Exception> consumer){
    
    
        this.exConsumer = consumer;
        if (this.thread != null && !this.thread.isAlive() && e != null){
    
    
            consumer.accept(e);
        }
    }

For example, the thenSuccess method requires a Consumer instance, and this instance also needs to be labeled. So what is this label? It depends on the label of the current object. For example, I have a man, and the label is a key. Then I need a tool for disposing of things, and this tool for disposing of things can also be labeled, for example, it can dispose of cups, dispose of wood, dispose of keys, etc. What this place means is that I need a Consumer type object. The label of this object is T, and where does T come from? T is defined when the current object is created, which is similar to the often said factory settings. This The instance knows its generics when it is created, that is, it is tagged when it is manufactured and shipped from the factory. Naturally, when this object is passed as a parameter, the compiler will automatically check whether the label matches a method parameter, etc.

Guess you like

Origin blog.csdn.net/qq_43750656/article/details/132541232