premise
JUC recently read a thread pool java.util.concurrent.ThreadPoolExecutor
source implementation, which learned java.util.concurrent.Future
of the realization of the principle. From the current java.util.concurrent.Future
implementation, 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 Future
the premise of the realization of the principle of extended support (listening) callback Future
, reference to the idea of Guava
an 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 ThreadPoolExecutor
defined 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 ExecutorService
the interface extended functions are based on Executor#execute()
the basis of expansion. Executor#execute()
The method is simply to put the task instance Runnable
object is put in the thread pool to assign the appropriate thread of execution, but because of the method is the return value void
type, we are unable to perceive when the task is finished. This time it is necessary for Runnable
the 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 Status
the 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 void
type of characteristics we can not make the callback Runnable
result 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 Runnable
parameters, we need to Callable
interface adapter to Runnable
interface 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 Callable
example of a direct delegate to Wrapper
, and Wrapper
implements Runnable
the interface, the execution result is stored directly in the defined Object
object type outcome
can be. When we perceive execution state has ended, you can from the outcome
extraction to the results.
Future implementations
The above is only a summary to Future
achieve a relatively reasonable to make virtual deduction, in fact, RunnableFuture
is the JUC commonly used in composite interfaces, it also implements Runnable
and Future
:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
On a fictitious mentioned Wrapper
type, in JUC similar implementation is java.util.concurrent.FutureTask
that it is Callable
and Runnable
the adapter FutureTask
implements RunnableFuture
the 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 state
that the implementation of state, outcome
the carrier execution result. Then look submission Callable
method 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 Callable
when the type of task, in fact, do the following steps:
- Check the parameters into
task
existence, if it isnull
thrownNullPointerException
. - The
Callable
type oftask
packagingFutureTask
instance. - The new
FutureTask
instance into the thread pool, which is invokedExecutor#execute(FutureTask实例)
. - Returns
FutureTask
Examples interface instanceRunnableFuture
(actually return sub-interfaceFuture
instance).
If we need to get results, may Future#get()
or Future#get(long timeout, TimeUnit unit)
acquisition, see call these two methods when FutureTask
there way to achieve, that steps are as follows:
- If the status
state
or lessCOMPLETING(1)
, indicating that the task is still running, the result acquisition request thread placedWaitNode
by blocking type queue. - If the task is finished, either normal or abnormal completion is completed, in addition to the update status
state
and assign the result to theoutcome
outside, but also wakes up all the threads blocked get results, then call the hook methodFutureTask#done()
(see specific source codeFutureTask#finishCompletion()
).
In fact, so much analysis, I want to point out to the conclusion that: Callable
after 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 Future
theoretical basis.
Future expansion may callback
Do first-pass encoding to achieve, then a simple test its functionality.
Coding
To define a Future
sub-interface interface ListenableFuture
for adding callback can monitor:
public interface ListenableFuture<V> extends Future<V> {
void addCallback(ListenableFutureCallback<V> callback, Executor executor);
}
ListenableFutureCallback
It is a functional callback interface:
@FunctionalInterface
public interface ListenableFutureCallback<V> {
void callback(V value, Throwable throwable);
}
For ListenableFutureCallback
, the callback results value
and throwable
are mutually exclusive. Under normal completed execution value
will be the value of the execution result, throwable
is null
; when an abnormality is finished, value
will be null
, throwable
it will be thrown exception instance. If the process is normally finished more accustomed and abnormal results of the finished result separately, ListenableFutureCallback
can be defined:
public interface ListenableFutureCallback<V> {
void onSuccess(V value);
void onError(Throwable throwable);
}
Then define ListenableExecutorService
interface inheritance ExecutorService
interfaces:
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 Runnable
interface 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 FutureTask
subclass 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 ThreadPoolExecutor
and implement ListenableExecutorService
interfaces:
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 Callable
the implementation throws an exception, the exception is packaging ExecutionException
, to call Throwable#getCause()
to get the original exception instance.
summary
By understanding ThreadPoolExecutor
and Future
doing 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)