Analysis of java Callable implementation principle

foreword

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

  1. Inherit Thread, rewrite the run method
  2. 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:

  1. Callable can provide a return value at the end of the task, Runnable cannot provide this function
  2. 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()

Write picture description here

(Figure 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 have implemented this method in the parent class, and follow up 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, 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:

Write picture description here

(Figure 4)


Write picture description here

(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:

Write picture description here

(Figure 6)

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

Write picture description here

(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:

Write picture description here

(Figure 8)

RunnableAdapter is an implementation class of the Callable interface:

Write picture description here

(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:

Write picture description here

(Figure 10)

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

Write picture description here

(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:

Write picture description here

(Figure 12)

Follow up:

Write picture description here

(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.

Write picture description here

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.

Guess you like

Origin blog.csdn.net/niuzhucedenglu/article/details/80525432