Summary of Java Basics (87)-Analysis of Callable Implementation Principles

Original link

Preface

There are generally two ways to create threads that we commonly use:

  1. Inherit Thread, rewrite the run method
  2. Implement Runnable interface, re-run method

In fact, there is another way to achieve asynchronous in the Executor framework, which is to implement the Callable interface and rewrite the call method. Although it implements Callable, when the Executor is actually running, instances of Runnable or Callable will be converted into instances of RunnableFuture, and RunnableFuture inherits the Runnable and Future interfaces, which will be explained in detail below. Knowing this, how is it different from Runnable? Callable is different from Runnable in the following two points:

  1. Callable can provide a return value at the end of the task, Runnable cannot provide this function
  2. Callable's call method can throw exceptions, while Runnable's run method cannot throw exceptions.

Realization principle

Before introducing the implementation principle of Callable, let's take a look at 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");
        }
    }

Run the above code and you will get the following results:

main start
// 这里会阻塞一段时间,才会打印下面的内容
MyCallable 线程:pool-1-thread-1
MyCallable
main end
  • 1
  • 2
  • 3
  • 4
  • 5

Through the above code, we have verified the difference between Callable and Runnable, so how is Callable implemented? First, we passed 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()

Write picture description here

(Picture 1)

We found that the implementation class of ExecutorService is FinalizableDelegatedExecutorService, and then follow up:

Write picture description here

(Figure II)

We found that there is no submit() method in FinalizableDelegatedExecutorService, so it must be implemented in the parent class, and follow up with the parent class:

Write picture description here

(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, calling it cyclically? Of course this is not the case. Note that e.submit here is a method of a local variable ExecutorService of DelegatedExecutorService, and e is assigned by the constructor. Now let us return to the FinalizableDelegatedExecutorService 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 following up, you will find that there is no submit() method in ThreadPoolExecutor. Then we have to look for the parent class:

Write picture description here

(Picture 4)


Write picture description here

(Picture 5)

From the AbstractExecutorService code, we can see that the Runnable interface and the Callable interface are processed in the same way as it is converted to RunnableFuture. And RunnableFuture is an interface, so it must have an implementation class to complete the conversion process. Let us follow these two newTaskFor respectively to see how Runnable and Callable instances are converted:

Write picture description here

(Picture 6)

From the code, we can see that the return value of the newTaskForjia() method is of the FutureTask type. Follow up again:

Write picture description here

(Picture 7)

The role of these two construction methods of FutureTask is to assign values ​​to callable and state. So far, Callable and Runnable instances are handled in the same way; the difference is that Runnable instances are passed to Executors.callable to generate Callable instances. Let’s take a look at this conversion process:

Write picture description here

(Picture 8)

RunnableAdapter is an implementation class of the Callable interface:

Write picture description here

(Picture 9)

RunnableAdapter overrides the call() method of Callable, and executes the run() method of Runnable when call() is executed; in this way, the unity of Runnable and Callable is achieved.

Now that Runnable and Callable are unified, let's look back at how the thread execution method execute (Runnable runnable)  in Figure 5  is implemented. what's the situation? What execute passes is an instance of Runnable, and we have unified Runnable and Callable into Callable. Does it feel strange? Note that in Figure 5, execute passes an instance of FutureTask, the implementation class of RunnableFuture. And RunnableFuture implements the Runnable interface and overrides Runnable's run() method:

Write picture description here

(Figure 10)

So, how does the implementation class FutureTask of Runnable implement the run() method?

Write picture description here

(Picture 11)

Callable.call() is called in the run() method, and run() is the overridden Runnable run() method. Now we understand why the execute() method in Figure 5 can pass the RunnableFuture instance: First, run the Runnable Or the instances of Callable are unified into the Callable type, and then call the call() method of Callable when the run() method is executed.

Attentive readers may have found a comment in the demo at the beginning of the article (// blocking will occur here), then what causes the blocking? Let us follow the implementation class FutureTask of Future (can be obtained from Figure 5) and see how its get() method is implemented:

Write picture description here

(Picture 12)

Continue to follow up:

Write picture description here

(Picture 13)

awaitDone is an infinite loop, only when the Callable or Runnable to be executed ends, it will jump out, so it is not difficult to understand the logic of Figure 12, when the Callable or Runnable has ended, the return value is directly given, otherwise it will block In the awaitDone() method, there is a comment in the comment.

Conclusion

This article has roughly explained the principle of Callable's implementation of asynchronous, for a more convenient understanding, here is a flowchart of using Callable.

Write picture description here

appendix

Regarding Executor is the content of the thread pool, it is not the focus of this article, just need to know: the thread pool needs to receive Runnable instances in use.

Reattach

Callable is rarely used 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 (denoted as thread A here) in order 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 too You can use Rxjava to achieve the same effect.

 

 

 

 

Guess you like

Origin blog.csdn.net/lsx2017/article/details/113922339