"Java hands-on source code" handwritten realization of Future design pattern
preface
Recently, I often encounter the Future design pattern. For example, when I look at the deployDescriptors method of XXXConfig class such as HostConfig of Tomcat source code; Here is the source code of deployDescriptors of HostConfig:
/** * Deploy XML context descriptors. * * @param configBase The config base * @param files The XML descriptors which should be deployed */ protected void deployDescriptors(File configBase, String[] files) { if (files == null) return; ExecutorService es = host.getStartStopExecutor(); //Future design pattern List<Future<?>> results = new ArrayList<>(); for (String file : files) { File contextXml = new File(configBase, file); if (file.toLowerCase(Locale.ENGLISH).endsWith(".xml")) { ContextName cn = new ContextName(file, true); if (isServiced(cn.getName()) || deploymentExists(cn.getName())) continue; results.add( es.submit(new DeployDescriptor(this, cn, contextXml))); } } for (Future<?> result : results) { try { result.get(); } catch (Exception e) { log.error(sm.getString( "hostConfig.deployDescriptor.threaded.error"), e); } } }
So what is the Future design pattern?
Assuming that a task needs to be executed for a long time, it usually needs to wait for the end of task execution or an error to return the result. During this period, the caller can only fall into blocking and wait. For this, the Future design pattern provides a credential solution. In our daily life, the use of credentials is very common. For example, when you go to a suit workshop and want to order a suit that fits your body, the production process of the suit is relatively long, ranging from a week to a month. You can't wait in place all the time. Generally speaking, the workshop will give you a credential, which is the Future. You can use this credential to get the suit on any day in the Future.
The author maintains a warehouse called Thread. He intends to handwrite some classic technologies of Java multithreading in this warehouse. Welcome to star. The code of this blog has been uploaded to the warehouse, which is on COM Thread. Under the future package.
Link: Warehouse address . Your motivation is to continue.
1, UML design
(1) Future provides two interfaces to obtain the calculation results and judge whether the task is completed. Obtaining the calculation results will lead to call blocking.
(2) FutureService is mainly used to submit tasks. There are three types of submitted tasks: one can return a value, one does not need a return value, and the other can execute a callback function
(3) The Task interface is mainly provided to the caller to implement the calculation logic. It can accept a parameter and return the final execution result.
(4) FutureTask is an implementation of Task. In addition to the get and done methods of Future, it also adds a protected type finish method, which is mainly used to notify the completion of interface tasks.
(5) The main function of FutureServiceImpl class is to create a new thread to accept the task when submitting the task, so as to achieve the effect of asynchronous execution of the task.
(6) The implementation of the CallBack interface is very simple, similar to the Consumer functional interface in jdk8 (accept a parameter, execute, and return no value)
2, Code implementation
Finally, the code is implemented, and the generated UML class diagram is as follows:
Here are the key codes:
2.1 FutureService
The code is as follows (example):
package com.thread.future; public interface FutureService<IN, OUT> { //You do not need to return a worthy task when submitting. Future The get method returns null Future<?> submit(Runnable runnable); // Submit tasks that need to be returned Future<OUT> submit(Task<IN, OUT> task, IN in); // Submit tasks that need to be returned Future<OUT> submit(Task<IN, OUT> taskM, IN in, CallBack<OUT> callBack); //Create an implementation of FutureService using static methods static <T, R> FutureService<T, R> newService() { return new FutureServiceImpl<>(); } }
2.3 FutureTask
The code is as follows (example):
package com.thread.future; public class FutureTask<T> implements Future<T> { //Calculation results private T result; //Whether the task is completed private boolean isDone = false; //Define object locks private final Object LOCK = new Object(); //This method will cause blocking until the end of the task @Override public T get() throws InterruptedException { synchronized (LOCK) { while (!isDone) { LOCK.wait(); } return result; } } //When the task is finished, assign the result to FutureTask protected void finish(T result) { synchronized (LOCK) { if (this.isDone) return; this.result = result; this.isDone = true; LOCK.notifyAll(); } } @Override public boolean done() { return isDone; } }
2.2 FutureServiceImpl
The code is as follows (example):
package com.thread.future; import java.util.concurrent.atomic.AtomicInteger; public class FutureServiceImpl<IN, OUT> implements FutureService<IN, OUT> { private final static String FUTURE_THREAD_PREFIX = "FUTURE-"; private final AtomicInteger nextCounter = new AtomicInteger(0); private String getNextName() { return FUTURE_THREAD_PREFIX + nextCounter.getAndIncrement(); } //For the submission method without return value, execute a run unit. @Override public Future<?> submit(Runnable runnable) { //Create a ticket final FutureTask<Void> futureTask = new FutureTask<>(); new Thread(() -> { // Execution logic unit runnable.run(); // After execution, the result is returned to the ticket. Because there is no return value, the return value is null futureTask.finish(null); }).start(); return futureTask; } //For the submission method with return value, the user-defined Task task is used to execute the get method and return the calculation result @Override public Future<OUT> submit(Task<IN, OUT> task, IN in) { //Create a ticket final FutureTask<OUT> futureTask = new FutureTask<>(); new Thread(() -> { // Execute the Task logical unit, enter a value, calculate, and then output the value OUT out = task.get(in); // After execution, the result is returned to the ticket. Because there is no return value, the return value is null futureTask.finish(out); }).start(); return futureTask; } //Submission method with callback function @Override public Future<OUT> submit(Task<IN, OUT> task, IN in, CallBack<OUT> callBack) { //Create a ticket final FutureTask<OUT> futureTask = new FutureTask<>(); new Thread(() -> { // Execute the Task logical unit, enter a value, calculate, and then output the value OUT out = task.get(in); // After execution, the result is returned to the ticket. Because there is no return value, the return value is null futureTask.finish(out); // After execution, execute the callback function if (callBack != null) { callBack.call(out); } }).start(); return futureTask; } }
For the specific code, go to my warehouse. I will update some technical source code implementations of Java multithreading from time to time in my warehouse. Such as thread pool, ThreaLocal, etc.
3, Code test
The test code is as follows:
package com.thread.future; public class FutureTest { public static void main(String[] args) throws InterruptedException { System.out.println("Here are three different callback methods tested"); System.out.println("First, the first one does not need to return a value:"); FutureService<Integer, Void> futureService1 = new FutureServiceImpl<>(); Future<?> future1 = futureService1.submit(() -> { System.out.println("The suit is finished....."); }); System.out.println("Return without return value:" + future1.get()); System.out.println("Secondly, the second one needs to return value:"); FutureService<Integer, String> futureService2 = new FutureServiceImpl<>(); Future<?> future2 = futureService2.submit(input -> { System.out.println("Suit according to your height" + input + "Notify the user when the message is finished"); return "The suit is finished"; }, 175); System.out.println("Return value required:" + future2.get()); System.out.println("Secondly, the third method based on time callback:"); FutureService<Integer, String> futureService3 = new FutureServiceImpl<>(); Future<?> future3 = futureService2.submit(input -> { System.out.println("Suit according to your height" + input + "Done"); return "The suit is finished"; }, 175, input -> { System.out.println("The suit is finished and delivered to your home according to the address you filled in"); }); System.out.println("Of callback function get Method is meaningless because everything you want to do is executed in the callback function"); } }
The output results are as follows:
Here are three different callback methods tested First, the first one does not need to return a value: The suit is finished..... Return without return value: null Secondly, the second one needs to return value: When the suit is finished according to your height, notify the user of this message Return value required: the suit is finished Secondly, the third method based on time callback: The suit is finished according to your height Of callback function get Method is meaningless because everything you want to do is executed in the callback function The suit is finished and delivered to your home according to the address you filled in
summary
When a task takes a long time to execute, the current thread blindly waits for the end of the task, which is a waste of CPU resources. During the waiting time, it can do other things, so the Future design model appears. Although the Future design model will not be blocked when submitting, it may still be blocked when obtaining results later. This paper writes three applications of the Future design model, namely with return value, without return value and callback function.