In the (1), (2) and (3) of the Java Multithreading Study Notes, we already have a basic understanding of multithreading, but the Runnable interface is still imperfect. If you want to get the execution result of the thread, then It may be a detour. In order to make up for the shortcomings, Java introduced the Callable interface, Callable is equivalent to an enhanced version of the Runnable interface. But if I want to cancel the task, Java introduced the Future class. But the Future is still not perfect, then enhance it: CompletionService.
Future and Callable
Callable, as an enhanced version of Runnable, can use another submit method of ThreadPoolExecutor to submit tasks. The statement of the submit method is as follows:
public <T> Future<T> submit(Callable<T> task)
The Callable method has only one method:
V call() throws Exception;
The return value of the call method represents the processing result of the corresponding task, and its type V is specified by the type parameter of the Callable interface; the task represented by the call method can throw an exception during its execution. The run method in the Runnable interface has neither return value nor throw exception. Executors.callable(Runnable task, T result) can convert Runnable interface to Callable interface instance.
Next, we will turn our attention to the return value Future interface. The Future interface instance can be regarded as the handle of the task submitted to the thread pool for execution. With the Future instance, we can get the return result of the thread execution. The Future.get() method can be used to obtain the processing result of the task specified by the task parameter. The declaration of the method is as follows:
V get() throws InterruptedException, ExecutionException;
When the Future.get() method is called, if the corresponding task has not been executed yet, Future.get() will pause the current thread until the execution of the corresponding task ends (including normal completion and termination by throwing an exception). Therefore, the Future.get() method is a blocking method, which can throw InterruptedException indicating that it can respond to interruption. Also assume that an arbitrary exception is thrown during the execution of the response task. Call the getCause method of this exception (ExecutionException) to return the exception thrown during task execution. Therefore, the client code can know the exception thrown during execution by catching the exception thrown by Future.get() call. Because the task is called Future.get() method to get the processing result of the task when the execution is not completed, it will cause the waiting for context switch, so if you need to get the execution result of the task, you should submit it to the thread pool as soon as possible, and call it as late as possible Future.get() method to get the processing result of the task, and the thread pool just uses this time to execute the submitted task.
The Future interface also supports the cancellation of tasks:
boolean cancel(boolean mayInterruptIfRunning);
If the task has been executed or has been canceled, or cannot be canceled for other reasons, the method will return false. If canceled successfully, the task will not be executed. The execution status of the task is divided into two types, one Is not yet implemented, one is already in implementation. For the task in execution, the parameter mayInterruptIfRunning represents whether to terminate the execution of the thread through interruption.
After this method is executed, if it returns true, the associated calls, isDone and isCancelled will always return true.
The Future.isDone() method can detect whether the response task is completed. The completion of the task execution, the throwing of an exception during the execution, and the cancellation will all cause the method to return true. Future.get() will make its execution thread wait unlimited until the execution of the corresponding task ends. But sometimes improper handling may lead to unlimited waiting. In order to avoid this kind of unlimited waiting, we can use another version of the get method at this time:
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
Specify the waiting time. If the waiting time has not been completed, this method will throw a TimeoutException. Note that the timeout specified by the method parameter is only used to control how long to wait for the processing result of the corresponding task at most, not to limit the execution time of the task itself. Therefore, the recommended approach is that the client usually needs to cancel the execution of the task after catching the TimeoutException.
Here is an example of Callable and Future:
public class FutureDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
Future<String> future = threadPool.submit(() -> {
TimeUnit.SECONDS.sleep(1);
int i = 1 / 0;
return "hello world";
});
String result = null;
try {
result = future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
// 打印出来的就是除0异常
System.out.println(e.getCause());
}
System.out.println(result); // 直到Future任务执行结束,线程返回执行结果, 这行才会被执行
threadPool.shutdownNow();
}
}
Asynchronous execution framework Executor
Runnable and Callable interfaces are the abstraction of task execution logic. We can write the corresponding task execution logic in the corresponding method. The java.util.concurretn.Executor interface is an abstraction of the execution of the task. The submitter of the task Only need to call the execute method of Executor can be executed, without having to care about the specific execution details.
For example, what to do before the task is executed, whether the task is executed by a dedicated thread or by the thread pool, and in what order the multiple tasks are executed. It can be seen that Executor can realize the decoupling of the specific details of task submission and task execution (Decoupling). Similar to the abstraction of task processing logic, the abstraction of task execution can also bring us the benefits of information hiding and separation of concerns.
So what are the benefits of this decoupling? An obvious example is the ability to shield the difference between synchronous execution and asynchronous execution to a certain extent. For the same task (instance of Runnable or Callable), if we submit this task to ThreadPoolExecutor (which implements Executor) for execution, it is asynchronous execution; if it is handed over to Executor as shown below for execution:
public class SynchronousExecutor implements Executor {
@Override
public void execute(Runnable command) {
}
}
Then the task is executed synchronously. So whether the task is executed synchronously or asynchronously, there is not much difference for the caller.
The Executor interface is relatively simple, and its functions are relatively limited:
- It cannot return the execution result of the task to the party submitting the task
- Executor's implementation class often maintains a certain number of worker threads. When we no longer need an Executor instance, we often need to stop its internally maintained worker threads to release the corresponding resources, but the Executor interface does not define the corresponding method. The current need is that we need to enhance the Executor interface, but we cannot directly change the Executor. The approach here is to make a sub-interface, which is ExecutorService. ExecutorService inherits Executor, and several submit methods are defined internally, which can return the execution result of the task. . The ExecutorService interface also defines the shutdown method and shutdownNow to shut down the corresponding service and release resources.
Further enhancement: CompletionService
Although the Future interface allows us to easily obtain the processing results of asynchronous tasks, if we need to submit the processing results of a batch of tasks at one time, the code written only using the Future interface will be quite cumbersome. The CompletionService interface was born to solve this problem. Let's briefly look at the methods provided by CompletionService:
The submit method is the same as a submit method of ThreadPoolExecutor:
Future<V> submit(Callable<V> task);
If you want to obtain the execution results of batch submission of asynchronous tasks, then we can use the method specifically defined for this purpose in the CompletionService interface:
Future<V> take() throws InterruptedException;
This method is similar to the take() of BlockingQueue. It is a blocking method, and its return value is a Future instance that has completed the task in batch submission of asynchronous tasks. We can get the execution result of asynchronous task through Future instance. If there is no task that has been executed when the take method is called, the thread that called the take method will be blocked, and the thread that called the take method will not be resumed until the execution of the asynchronous task ends.
If you want to obtain the result of an asynchronous task, but if there is no completed asynchronous task, CompletionService also provides a non-blocking method to obtain the processing result of the asynchronous task:
- Future<V> poll(); // If there is no completed task, return null
- Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException; // There is no completed asynchronous task within the specified time, and null is returned.
The implementation class of CompletionService provided by the Java standard library is ExecutorCompletionService. Contacting the Executor mentioned above, we can roughly infer that this class is a combination of Executor and CompletionService. Let's randomly find a constructor of ExecutorCompletionService to take a look at it roughly: public ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue)
The executor is the executor of the task. After the task is executed, the Future instance of the execution result is put into the BlockingQueue
Callable supplement: Overview of FutureTask
Whether it is a task represented by a Runnable instance or a Callable instance, as long as we submit it to the thread pool for execution, then I can consider this task as an asynchronous task. The advantage of using Runnable instances to represent asynchronous tasks is that the task can be handed over to a dedicated worker thread for execution or submitted to the thread pool for execution. The disadvantage is that we cannot directly obtain the execution result of the task. Using Callable instances to represent asynchronous tasks, the advantage is that we can get the execution results of the task through the return value of ThreadPoolExecutor, but it cannot be directly handed over to a dedicated worker thread for execution. Therefore, the use of Callable to represent asynchronous tasks will greatly limit the execution of tasks.
The FutureTask class of java.util.concurrent combines the advantages of Runnable and Callable interfaces: FutureTask is an implementation class of RunnableFuture.
Runnable is a sub-interface of Runable and Future. Therefore, FutureTask is both an implementation class of Runable and an implementation of Future interface. FutureTask provides a constructor to convert a Callable instance to a Runnable instance. The declaration of the constructor is as follows:
public FutureTask(Callable<V> callable)
This makes up for the shortcomings that the Callable instance can only be handed over to the thread pool for execution and cannot be handed over to the specified worker thread for execution, and the FutureTask instance itself also represents the task we want to perform, we can get the previous Runnable instance through the FutureTask instance. The thread execution result. For example, Executor.execute(Runnable task) only recognizes Runnable instances to execute tasks, you can still get the execution results.
Here is an example:
public class FutureTaskDemo {
public static void main(String[] args) throws Exception{
FutureTask<String> futureTask = new FutureTask(()->{
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello world");
return "hello world";
});
new Thread(futureTask).start();
while (futureTask.isDone()){
System.out.println(futureTask.get());
}
}
}
FutureTask also supports processing the execution result of the task in a callback mode. When the task represented by the FutureTask instance is executed, Future.done will be executed. The execution thread of FutureTask.done and the execution thread of FutureTask's run are the same thread. In FutureTask, the protected method is empty. We can subclass This method is rewritten to implement subsequent callback processing. Since the done method is triggered after the task is executed, calling the get method in the done method to obtain the task execution result will not be blocked. However, because the end of the task execution includes normal termination, abnormal termination and termination caused by task cancellation, the code in the FutureTask.done method may need to call FutureTask.isCancelled before calling FutureTask.get() to determine the task. Whether to be canceled, so as not to throw CancellationException when calling FutureTask.get.
Simple example of FutureTask:
public class FutureTaskDemo extends FutureTask<String>{
public FutureTaskDemo(Callable<String> callable) {
super(callable);
}
public FutureTaskDemo(Runnable runnable, String result) {
super(runnable, result);
}
/**
* 任务执行结束该方法会自动被调用
* 我们通过get方法获取线程的执行结果,
* 由于该方法在任务执行结束被调用,所以该方法调用get不会被阻塞
*/
@Override
protected void done() {
try {
String threadResult = get();
System.out.println(threadResult);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
FutureTaskDemo futureTaskDemo = new FutureTaskDemo(()->{
System.out.println("hello world");
return "hello world";
});
new Thread(futureTaskDemo).start();
}
}
FutureTask still has some shortcomings. It is basically designed to represent a one-time execution task. It internally maintains a state variable that represents the running state of the task (including not started running, already running, etc.), FutureTask.run When executing run, it will first judge the execution status of the corresponding task:
If it has been executed, it will return directly without throwing an exception. Therefore, the task represented by the FutureTask instance cannot be executed repeatedly. This means that the same FutureTask instance cannot be submitted for execution multiple times, although no exceptions will be thrown. FutureTask.runAndReset can break this limitation, so that the task represented by a FutureTask instance is executed multiple times, but the execution result of the task is not recorded. Therefore, if the task represented by the same object needs to be executed multiple times, and we need to process the execution result of the task, then FutureTask is not applicable, but we can imitate FutureTask to design one that meets our needs, and this is also We look at the meaning of the source code, and focus on understanding and learning the design thinking behind it, which leads us to AsyncTask. Example:
public class FutureTaskDemo extends FutureTask<String>{
public FutureTaskDemo(Callable<String> callable) {
super(callable);
}
public FutureTaskDemo(Runnable runnable, String result) {
super(runnable, result);
}
@Override
public void run() {
// 启动任务用runAndReset启用
super.runAndReset();
}
/**
* 任务执行结束该方法会自动被调用
* 我们通过get方法获取线程的执行结果,
* 由于该方法在任务执行结束被调用,所以该方法调用get不会被阻塞
*/
@Override
protected void done() {
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTaskDemo futureTaskDemo = new FutureTaskDemo(()->{
System.out.println("hello world");
return "hello world";
});
new Thread(futureTaskDemo).start();
new Thread(futureTaskDemo).start();
}
}
Repeatable asynchronous tasks: AsyncTask
public abstract class AsyncTask<V> implements Runnable, Callable<V> {
protected final Executor executor;
public AsyncTask(Executor executor) {
this.executor = executor;
}
public AsyncTask() {
this(new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
});
}
@Override
public void run() {
Exception exp = null;
V r = null;
try {
//call的逻辑可以从传递也可以由自己来实现
r = call();
}catch (Exception e){
exp = e;
}
final V result = r;
if (null == exp){
executor.execute(new Runnable() {
@Override
public void run() {
onResult(result);
}
});
}else {
executor.execute(new Runnable() {
@Override
public void run() {
onError(result);
}
});
}
}
/**
*留给子类实现任务执行结果的处理逻辑
* @param result
*/
protected abstract void onError(V result);
/**
* 留给子类实现任务执行结果的处理逻辑
* @param result
*/
protected abstract void onResult(V result);
}
Reference
- "Java Multithreaded Programming Practical Guide" Huang Wenhai
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。