foreword
There are generally two ways to create threads that we commonly use:
- Inherit Thread, rewrite the run method
- Implement the Runnable interface and rerun the method
In fact, there is another way to achieve asynchrony in the Executor framework, which is to implement the Callable interface and rewrite the call method. Although Callable is implemented, when Executor is actually running, instances of Runnable or Callable will be converted into instances of RunnableFuture, and RunnableFuture inherits Runnable and Future interfaces, which will be explained in detail below. Knowing this, how is it different from Runnable? Callable has the following two differences compared with Runnable:
- Callable can provide a return value at the end of the task, Runnable cannot provide this function
- The call method of Callable can throw exceptions, but the run method of Runnable cannot throw exceptions.
Realization principle
Before introducing the implementation principle of Callable, let's see how it is used:
public class ThreadTest {
public static void main(String[] args) {
System.out.println("main start");
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// Future<?> future = threadPool.submit(new MyRunnable()) ;
Future<String> future = threadPool.submit(new MyCallable());
try {
// 这里会发生阻塞
System.out.println(future.get());
} catch (Exception e) {
} finally {
threadPool.shutdown();
}
System.out.println("main end");
}
}
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 模拟耗时任务
Thread.sleep(3000);
System.out.println("MyCallable 线程:" + Thread.currentThread().getName());
return "MyCallable" ;
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
// 模拟耗时任务
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MyRunnable");
}
}
Running the above code you will get the following result:
main start
// 这里会阻塞一段时间,才会打印下面的内容
MyCallable 线程:pool-1-thread-1
MyCallable
main end
Through the above code, we have verified the difference between Callable and Runnable, so how is Callable implemented? First, we pass the Callable implementation class MyCallable to ExecutorService.submit() and ExecutorService is an interface, so it must override the submit() method in its implementation class; follow up: Executors.newSingleThreadExecutor()
(Figure 1)
We found that the implementation class of ExecutorService is FinalizableDelegatedExecutorService, and then follow up:
(Figure II)
We found that there is no submit() method in FinalizableDelegatedExecutorService, so it must have implemented this method in the parent class, and follow up the parent class:
(Picture 3)
From DelegatedExecutorService, we can see that the implementation of its submit() method calls ExecutorService.submit() . Does it feel like it's back to the original point, cyclic call? Of course not. Note that e.submit here is a method of a local variable ExecutorService of DelegatedExecutorService, and e is assigned by the construction method. Now let's go back to FinalizableDelegatedExecutorService, a subclass of DelegatedExecutorService, to see what is passed in the construction method. From Figure 2 and Figure 1, we can see that the ExecutorService implementation class passed in the construction method is ThreadPoolExecutor. Finally found the right owner, let us follow up how ThreadPoolExecutor implements submit(). After follow-up, you will find that there is no submit() method in ThreadPoolExecutor, so we have to find it in the parent class:
(Figure 4)
(Figure 5)
From the AbstractExecutorService code, we can see that the Runnable interface is processed in the same way as the Callable interface by converting it into a RunnableFuture. And RunnableFuture is an interface, so it must have an implementation class to complete the conversion process. Let us follow up these two newTaskFor to see how the instances of Runnable and Callable are converted:
(Figure 6)
From the code, we can see that the return value of the newTaskForjia() method is of type FutureTask, follow up again:
(Figure 7)
The function of these two constructors of FutureTask is to assign values to callable and state. So far, the processing of Callable and Runnable instances is the same; the difference is that Runnable instances are passed to Executors.callable to generate Callable instances. Let's take a look at the transformation process:
(Figure 8)
RunnableAdapter is an implementation class of the Callable interface:
(Figure 9)
RunnableAdapter overrides Callable's call() method, and executes Runnable's run() method when executing call(); in this way, Runnable and Callable are unified.
Now that Runnable and Callable are unified, let's look back at how execute(Runnable runnable) in Figure 5 of the thread execution method is implemented. what's the situation? Execute passes the instance of Runnable, and we unify Runnable and Callable into Callable. Does it feel weird? Note that what execute in Figure 5 passes is the instance of FutureTask, the implementation class of RunnableFuture. And RunnableFuture implements the Runnable interface and overrides Runnable's run() method:
(Figure 10)
So, how does FutureTask, the implementation class of Runnable, implement the run() method?
(Figure 11)
Callable.call() is called in the run() method, and run() is the overridden Runnable's run() method. Now you can understand why the execute() method in Figure 5 can pass the RunnableFuture instance: First, the Runnable Or the instance of Callable is unified as Callable type, and then the call() method of Callable is called when the run() method is executed.
Careful readers may have found a comment in the demo at the beginning of the article (//blocking will occur here), so what causes the blocking? Let's follow up Future's implementation class FutureTask (obtained from Figure 5) to see how its get() method is implemented:
(Figure 12)
Follow up:
(Figure 13)
awaitDone is an infinite loop, it will jump out only when the Callable or Runnable to be executed ends, so it is not difficult to understand the logic in Figure 12, when the Callable or Runnable has ended, return the value directly, otherwise it will block In the awaitDone() method, so there is the comment in the comment.
epilogue
This article has roughly explained the principle of Callable to realize asynchrony. For a more convenient understanding, here is a flow chart of using Callable.
appendix
The Executor belongs to the thread pool and is not the focus of this article. You only need to know that the thread pool needs to receive the Runnable instance when it is in use.
attach again
Callable has very few usage scenarios in daily development, and it is not so vivid to understand. Here is an example: In concurrent network access, we sometimes need the results of multiple access requests together as the parameters of the next method, then Callable can be used at this time; if it is in Android, pay attention to wrapping these requests with a non- UI thread (referred to as thread A here) to avoid blocking the UI thread when calling. When all network access in thread A is over (including other time-consuming operations), you can send a message to the main thread to refresh the UI. Of course, you can also You can use Rxjava to achieve the same effect.