Java Concurrency - can be extended callback Future

premise

JUC recently read a thread pool java.util.concurrent.ThreadPoolExecutorsource implementation, which learned java.util.concurrent.Futureof the realization of the principle. From the current java.util.concurrent.Futureimplementation, although the realization of asynchronous submit the task, but the process of obtaining results of the task requires active call Future#get()or Future#get(long timeout, TimeUnit unit)while the former is blocked, the latter is possible in the case of asynchronous task execution times of uncertainty need to poll, in both cases and asynchronous calls a little contrary to the original intention. So I want to combine the current understanding of Futurethe premise of the realization of the principle of extended support (listening) callback Future, reference to the idea of Guavaan enhanced ListenableFuture. JDK preparation of this paper using JDK11, others might not be suitable.

The principle is simple analysis of Future

Examples of virtual deduction

Concurrent Master Doug Lea in the design of JUC thread pool provides a top-level actuator interface Executor:

public interface Executor {

    void execute(Runnable command);
}    

In fact, the method defined here Executor#execute()is the core interface to the entire system thread pool, which is ThreadPoolExecutordefined by the core threads, additional threads created (maximum thread pool thread capacity - the core number of threads) are lazy to create an interface at this time to submit the task , which means that ExecutorServicethe interface extended functions are based on Executor#execute()the basis of expansion. Executor#execute()The method is simply to put the task instance Runnableobject is put in the thread pool to assign the appropriate thread of execution, but because of the method is the return value voidtype, we are unable to perceive when the task is finished. This time it is necessary for Runnablethe task instance packaging (The following is pseudo code + pseudo-logic):

// 下面这个Wrapper和Status类是笔者虚构出来
@RequiredArgsConstructor
class Wrapper implements Runnable{

    private final Runnable target;
    private Status status = Status.of("初始化");

    @Override
    public void run(){
        try{
           target.run();
           status = Status.of("执行成功");
        }catch(Throwable t){
           status = Status.of("执行异常"); 
        }
    }
}

We just need to new Wrapper(原始Runnable实例)put into execution thread pool, so defined by Statusthe state record variable can know the status of asynchronous task execution, and when finished (including finished finished normal and abnormal). Here are just solve the task execution state acquisition, but Executor#execute()the return value method method is the voidtype of characteristics we can not make the callback Runnableresult object. This time you need to define a callback interface the results can, in fact, already have an existing interface Callable:

@FunctionalInterface
public interface Callable<V> {

    V call() throws Exception;
}    

Here encountered a problem: Because Executor#execute()only receives Runnableparameters, we need to Callableinterface adapter to Runnableinterface this time, the commission can do a simple:

@RequiredArgsConstructor
class Wrapper implements Runnable{

    private final Callable callable;
    private Status status = Status.of("初始化");
    @Getter
    private Object outcome;

    @Override
    public void run(){
        try{
           outcome = callable.call();
           status = Status.of("执行成功");
        }catch(Throwable t){
           status = Status.of("执行异常"); 
           outcome = t;
        }
    }
}

Here the Callableexample of a direct delegate to Wrapper, and Wrapperimplements Runnablethe interface, the execution result is stored directly in the defined Objectobject type outcomecan be. When we perceive execution state has ended, you can from the outcomeextraction to the results.

Future implementations

The above is only a summary to Futureachieve a relatively reasonable to make virtual deduction, in fact, RunnableFutureis the JUC commonly used in composite interfaces, it also implements Runnableand Future:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    
    void run();
}

On a fictitious mentioned Wrappertype, in JUC similar implementation is java.util.concurrent.FutureTaskthat it is Callableand Runnablethe adapter FutureTaskimplements RunnableFuturethe interface:

public class FutureTask<V> implements RunnableFuture<V> {

    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

    /** The underlying callable; nulled out after running */
    private Callable<V> callable;
    /** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes
    /** The thread running the callable; CASed during run() */
    private volatile Thread runner;
    /** Treiber stack of waiting threads */
    private volatile WaitNode waiters;
    
    // 省略其他代码
}    

The core properties noted statethat the implementation of state, outcomethe carrier execution result. Then look submission Callablemethod kinds of tasks ExecutorService#submit():

public interface ExecutorService extends Executor {

    // 省略其他接口方法

    <T> Future<T> submit(Callable<T> task);
}    

When we pass the above ExecutorService#submit()submission method Callablewhen the type of task, in fact, do the following steps:

  1. Check the parameters into taskexistence, if it is nullthrown NullPointerException.
  2. The Callabletype of taskpackaging FutureTaskinstance.
  3. The new FutureTaskinstance into the thread pool, which is invoked Executor#execute(FutureTask实例).
  4. Returns FutureTaskExamples interface instance RunnableFuture(actually return sub-interface Futureinstance).

If we need to get results, may Future#get()or Future#get(long timeout, TimeUnit unit)acquisition, see call these two methods when FutureTaskthere way to achieve, that steps are as follows:

  1. If the status stateor less COMPLETING(1), indicating that the task is still running, the result acquisition request thread placed WaitNodeby blocking type queue.
  2. If the task is finished, either normal or abnormal completion is completed, in addition to the update status stateand assign the result to the outcomeoutside, but also wakes up all the threads blocked get results, then call the hook method FutureTask#done()(see specific source code FutureTask#finishCompletion()).

In fact, so much analysis, I want to point out to the conclusion that: Callableafter the type of tasks submitted to the finished thread pool (including normal and abnormal finished finished) will hook callback methodFutureTask#done() . This is what we can listen to expand Futuretheoretical basis.

Future expansion may callback

Do first-pass encoding to achieve, then a simple test its functionality.

Coding

To define a Futuresub-interface interface ListenableFuturefor adding callback can monitor:

public interface ListenableFuture<V> extends Future<V> {

    void addCallback(ListenableFutureCallback<V> callback, Executor executor);
}

ListenableFutureCallbackIt is a functional callback interface:

@FunctionalInterface
public interface ListenableFutureCallback<V> {

    void callback(V value, Throwable throwable);
}

For ListenableFutureCallback, the callback results valueand throwableare mutually exclusive. Under normal completed execution valuewill be the value of the execution result, throwableis null; when an abnormality is finished, valuewill be null, throwableit will be thrown exception instance. If the process is normally finished more accustomed and abnormal results of the finished result separately, ListenableFutureCallbackcan be defined:

public interface ListenableFutureCallback<V> {

    void onSuccess(V value);

    void onError(Throwable throwable);
}

Then define ListenableExecutorServiceinterface inheritance ExecutorServiceinterfaces:

public interface ListenableExecutorService extends ExecutorService {

    <T> ListenableFuture<T> listenableSubmit(Callable<T> callable);

    /**
     * 定义这个方法是因为有些时候由于任务执行时间非常短,有可能通过返回的ListenableFuture实例添加回调之前已经执行完毕,因此可以支持显式传入回调
     *
     * @param callable  callable
     * @param callbacks callbacks
     * @param executor  executor
     * @return ListenableFuture
     */
    <T> ListenableFuture<T> listenableSubmit(Callable<T> callable, List<ListenableFutureCallback<T>> callbacks, Executor executor);
}

Then add one execution unit adapter ListenableFutureCallbackRunnable, carrying calls each time a trigger callback (implementation Runnableinterface to support asynchronous execution):

@RequiredArgsConstructor
public class ListenableFutureCallbackRunnable<V> implements Runnable {

    private final ListenableFutureCallback<V> callback;
    private final V value;
    private final Throwable throwable;

    @Override
    public void run() {
        callback.callback(value, throwable);
    }
}

Then you need to define a FutureTasksubclass ListenableFutureTask, core logic covering FutureTask#done()method callback is triggered:

// ListenableFutureTask
public class ListenableFutureTask<V> extends FutureTask<V> implements ListenableFuture<V> {

    private final List<Execution<V>> executions = new ArrayList<>();

    public ListenableFutureTask(Callable<V> callable) {
        super(callable);
    }

    public ListenableFutureTask(Runnable runnable, V result) {
        super(runnable, result);
    }

    public static <V> ListenableFutureTask<V> newTaskFor(Callable<V> callable) {
        return new ListenableFutureTask<>(callable);
    }

    @Override
    protected void done() {
        Iterator<Execution<V>> iterator = executions.iterator();
        Throwable throwable = null;
        V value = null;
        try {
            value = get();
        } catch (Throwable t) {
            throwable = t;
        }
        while (iterator.hasNext()) {
            Execution<V> execution = iterator.next();
            ListenableFutureCallbackRunnable<V> callbackRunnable = new ListenableFutureCallbackRunnable<>(execution.getCallback(),
                    value, throwable);
            // 异步回调
            if (null != execution.getExecutor()) {
                execution.getExecutor().execute(callbackRunnable);
            } else {
                // 同步回调
                callbackRunnable.run();
            }
        }
    }

    @Override
    public void addCallback(ListenableFutureCallback<V> callback, Executor executor) {
        Execution<V> execution = new Execution<>();
        execution.setCallback(callback);
        execution.setExecutor(executor);
        executions.add(execution);
    }
}

// Execution - 承载每个回调实例和对应的Executor,Executor实例为null则进行同步回调
@Data
public class Execution <V>{

    private Executor executor;
    private ListenableFutureCallback<V> callback;
}

The final step is to write a thread pool ListenableThreadPoolExecutor, inherited from ThreadPoolExecutorand implement ListenableExecutorServiceinterfaces:

public class ListenableThreadPoolExecutor extends ThreadPoolExecutor implements ListenableExecutorService {

    public ListenableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public ListenableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, 
    BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public ListenableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, 
    BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public ListenableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
     BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    @Override
    public <T> ListenableFuture<T> listenableSubmit(Callable<T> callable) {
        if (null == callable) {
            throw new IllegalArgumentException("callable");
        }
        ListenableFutureTask<T> listenableFutureTask = ListenableFutureTask.newTaskFor(callable);
        execute(listenableFutureTask);
        return listenableFutureTask;
    }

    @Override
    public <T> ListenableFuture<T> listenableSubmit(Callable<T> callable, List<ListenableFutureCallback<T>> callbacks, Executor executor) {
        if (null == callable) {
            throw new IllegalArgumentException("callable");
        }
        if (null == callbacks) {
            throw new IllegalArgumentException("callbacks");
        }
        ListenableFutureTask<T> listenableFutureTask = ListenableFutureTask.newTaskFor(callable);
        for (ListenableFutureCallback<T> callback : callbacks) {
            listenableFutureTask.addCallback(callback, executor);
        }
        execute(listenableFutureTask);
        return listenableFutureTask;
    }
}

test

The introduction junit, write test classes are as follows:

public class ListenableFutureTest {

    private static ListenableExecutorService EXECUTOR;
    private static Executor E;

    @BeforeClass
    public static void before() {
        EXECUTOR = new ListenableThreadPoolExecutor(1, 3, 0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10), new ThreadFactory() {

            private final AtomicInteger counter = new AtomicInteger();

            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName(String.format("ListenableWorker-%d", counter.getAndIncrement()));
                return thread;
            }
        });
        E = Executors.newFixedThreadPool(3);
    }

    @Test
    public void testListenableFuture1() throws Exception {
        ListenableFuture<String> future = EXECUTOR.listenableSubmit(() -> {
            Thread.sleep(1000);
            return "message";
        });
        future.addCallback((v, t) -> {
            System.out.println(String.format("Value = %s,Throwable = %s", v, t));
        }, null);
        Thread.sleep(2000);
    }

    @Test
    public void testListenableFuture2() throws Exception {
        ListenableFuture<String> future = EXECUTOR.listenableSubmit(() -> {
            Thread.sleep(1000);
            throw new RuntimeException("exception");
        });
        future.addCallback((v, t) -> {
            System.out.println(String.format("Value = %s,Throwable = %s", v, t));
        }, null);
        Thread.sleep(2000);
    }

    @Test
    public void testListenableFuture3() throws Exception {
        ListenableFuture<String> future = EXECUTOR.listenableSubmit(() -> {
            Thread.sleep(1000);
            return "message";
        });
        future.addCallback((v, t) -> {
            System.out.println(String.format("Value = %s,Throwable = %s", v, t));
        }, E);
        System.out.println("testListenableFuture3 end...");
        Thread.sleep(2000);
    }

    @Test
    public void testListenableFuture4() throws Exception {
        ListenableFuture<String> future = EXECUTOR.listenableSubmit(() -> {
            Thread.sleep(1000);
            throw new RuntimeException("exception");
        });
        future.addCallback((v, t) -> {
            System.out.println(String.format("Value = %s,Throwable = %s", v, t));
        }, E);
        System.out.println("testListenableFuture4 end...");
        Thread.sleep(2000);
    }
}

Results of the:

// testListenableFuture1
Value = message,Throwable = null

// testListenableFuture2
Value = null,Throwable = java.util.concurrent.ExecutionException: java.lang.RuntimeException: exception

// testListenableFuture3
testListenableFuture3 end...
Value = message,Throwable = null

// testListenableFuture4
testListenableFuture4 end...
Value = null,Throwable = java.util.concurrent.ExecutionException: java.lang.RuntimeException: exception

And the expected results are consistent, note if Callablethe implementation throws an exception, the exception is packaging ExecutionException, to call Throwable#getCause()to get the original exception instance.

summary

By understanding ThreadPoolExecutorand Futuredoing simple extension of the realization of the principle, so that submitting asynchronous tasks become more elegant and simple. Strengthen the ability, but also to deepen some cognitive concurrent programming. Of course, this only provides a very simple realization, in fact, I also thought of as the callback for time-consuming process of doing the monitoring, packet label marked callback execution, and so better features, there is a need to wait until then to realize the scene.

Here the record about the process some insight:

  • Executor#execute()Is the core interface thread pool, all other functions are based on this interface to do the expansion, its design itself is stateless.
  • Flexible use an adapter mode, you can not change the function of the interface has been released at the same time to achieve a new interface feature adaptation.
  • To good to explore and use the JDK class library designers left expansion interface developers.

personal blog

(Herein End c-1-d ea-20190702)

Guess you like

Origin www.cnblogs.com/throwable/p/12319626.html