Analyze the top-level interfaces and abstract classes in the thread pool from the source code point of view

华为云开发者社区
中文
Abstract: 16125c04c71852 Let’s take a look at those very important interfaces and abstract classes in the thread pool, and analyze in depth how the idea of abstraction is used to the

This article is shared from Huawei Cloud Community " [High Concurrency] In-depth analysis of those important top-level interfaces and abstract classes thread pool", author: Binghe.

Through the analysis of the interfaces and abstract classes in the thread pool, you will find that the design of the entire thread pool is so elegant and powerful. From the code design of the thread pool, we learn more than just the code! !

Digression: Worship the Java god Doug Lea. The concurrency package in Java was written by this old man. He is the person with the greatest influence on Java in the world.

1. Overview of interfaces and abstract classes

Speaking of the important interfaces and abstract classes provided in the thread pool, they are basically the interfaces and classes shown in the figure below.
image.png

A brief description of interfaces and classes:

  • Executor interface: This interface is also the top-level interface in the entire thread pool, providing a method for submitting tasks without return value.
  • ExecutorService interface: Derived from the Executor interface, extended functions, such as closing the thread pool, submitting tasks and returning result data, waking up tasks in the thread pool, etc.
  • AbstractExecutorService abstract class: Derived from the ExecutorService interface, it implements several very implemented methods for subclasses to call.
  • The ScheduledExecutorService timing task interface, derived from the ExecutorService interface, has all the methods defined by the ExecutorService interface and extends the methods related to timing tasks.

Next, we will look at what functions these interfaces and abstract classes provide from the top-level design from the source code point of view.

Two, Executor interface

The source code of the Executor interface is shown below.

public interface Executor {
    //提交运行任务,参数为Runnable接口对象,无返回值
    void execute(Runnable command);
}

As can be seen from the source code, the Executor interface is very simple, and only provides an execute(Runnable) method for submitting tasks with no return value.

Because this interface is too simple, we cannot know the execution result data of the thread pool. If we no longer use the thread pool, we cannot close the thread pool through the Executor interface. At this point, we need the support of the ExecutorService interface.

Three, ExecutorService interface

The ExecutorService interface is the core interface of the non-timed task thread pool. Through the ExecutorService interface, you can submit tasks to the thread pool (supporting return results and no results), close the thread pool, and wake up tasks in the thread pool. The source code of the ExecutorService interface is shown below.

package java.util.concurrent;
import java.util.List;
import java.util.Collection;
public interface ExecutorService extends Executor {

    //关闭线程池,线程池中不再接受新提交的任务,但是之前提交的任务继续运行,直到完成
    void shutdown();
 
    //关闭线程池,线程池中不再接受新提交的任务,会尝试停止线程池中正在执行的任务。
    List<Runnable> shutdownNow();
 
    //判断线程池是否已经关闭
    boolean isShutdown();
 
    //判断线程池中的所有任务是否结束,只有在调用shutdown或者shutdownNow方法之后调用此方法才会返回true。
    boolean isTerminated();

    //等待线程池中的所有任务执行结束,并设置超时时间
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
 
    //提交一个Callable接口类型的任务,返回一个Future类型的结果
    <T> Future<T> submit(Callable<T> task);
 
    //提交一个Callable接口类型的任务,并且给定一个泛型类型的接收结果数据参数,返回一个Future类型的结果
    <T> Future<T> submit(Runnable task, T result);

    //提交一个Runnable接口类型的任务,返回一个Future类型的结果
    Future<?> submit(Runnable task);

    //批量提交任务并获得他们的future,Task列表与Future列表一一对应
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
 
    //批量提交任务并获得他们的future,并限定处理所有任务的时间
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit) throws InterruptedException;
 
    //批量提交任务并获得一个已经成功执行的任务的结果
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException; 
 
    //批量提交任务并获得一个已经成功执行的任务的结果,并限定处理任务的时间
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Regarding the meaning of each method in the ExecutorService interface, just refer to the comments in the above interface source code. These interface methods are relatively simple, and I will not repeat them one by one. This interface is also the most commonly used interface in thread pools that use non-timed task classes.

Four, AbstractExecutorService abstract class

The AbstractExecutorService class is an abstract class, derived from the ExecutorService interface, on the basis of which several more practical methods are implemented, which are provided for subclasses to call. Let's take a look at the source code of the AbstractExecutorService class.

Note: You can go to the java.util.concurrent package to view the complete source code of the AbstractExecutorService class. Here, I will disassemble the source code of the AbstractExecutorService to explain the function of each method in detail.

newTaskFor method

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

The RunnableFuture class is used to obtain the execution result. In actual use, we often use its subclass FutureTask. The function of the newTaskFor method is to encapsulate the task into a FutureTask object, and then submit the FutureTask object to the thread pool.

doInvokeAny method

private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                          boolean timed, long nanos)
    throws InterruptedException, ExecutionException, TimeoutException {
    //提交的任务为空,抛出空指针异常
    if (tasks == null)
        throw new NullPointerException();
    //记录待执行的任务的剩余数量
    int ntasks = tasks.size();
    //任务集合中的数据为空,抛出非法参数异常
    if (ntasks == 0)
        throw new IllegalArgumentException();
    ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
    //以当前实例对象作为参数构建ExecutorCompletionService对象
    // ExecutorCompletionService负责执行任务,后面调用用poll返回第一个执行结果
    ExecutorCompletionService<T> ecs =
        new ExecutorCompletionService<T>(this);

    try {
        // 记录可能抛出的执行异常
        ExecutionException ee = null;
        // 初始化超时时间
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        Iterator<? extends Callable<T>> it = tasks.iterator();
 
        //提交任务,并将返回的结果数据添加到futures集合中
        //提交一个任务主要是确保在进入循环之前开始一个任务
        futures.add(ecs.submit(it.next()));
        --ntasks;
        //记录正在执行的任务数量
        int active = 1;

        for (;;) {
            //从完成任务的BlockingQueue队列中获取并移除下一个将要完成的任务的结果。
            //如果BlockingQueue队列中中的数据为空,则返回null
            //这里的poll()方法是非阻塞方法
            Future<T> f = ecs.poll();
            //获取的结果为空
            if (f == null) {
                //集合中仍有未执行的任务数量
                if (ntasks > 0) {
                    //未执行的任务数量减1
                    --ntasks;
                    //提交完成并将结果添加到futures集合中
                    futures.add(ecs.submit(it.next()));
                    //正在执行的任务数量加•1
                    ++active;
                }
                //所有任务执行完成,并且返回了结果数据,则退出循环
                //之所以处理active为0的情况,是因为poll()方法是非阻塞方法,可能导致未返回结果时active为0
                else if (active == 0)
                    break;
                //如果timed为true,则执行获取结果数据时设置超时时间,也就是超时获取结果表示
                else if (timed) {    
                    f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
                    if (f == null)
                        throw new TimeoutException();
                    nanos = deadline - System.nanoTime();
                }
                //没有设置超时,并且所有任务都被提交了,则一直阻塞,直到返回一个执行结果
                else
                    f = ecs.take();
            }
            //获取到执行结果,则将正在执行的任务减1,从Future中获取结果并返回
            if (f != null) {
                --active;
                try {
                    return f.get();
                } catch (ExecutionException eex) {
                    ee = eex;
                } catch (RuntimeException rex) {
                    ee = new ExecutionException(rex);
                }
            }
        }

        if (ee == null)
            ee = new ExecutionException();
        throw ee;

    } finally {
        //如果从所有执行的任务中获取到一个结果数据,则取消所有执行的任务,不再向下执行
        for (int i = 0, size = futures.size(); i < size; i++)
            futures.get(i).cancel(true);
    }
}

This method is the core method that batches the tasks of the thread pool and finally returns a result data. Through the analysis of the source code, we can find that this method will cancel all running tasks in the thread pool as long as it obtains a result data. Return the result data. This is like a lot of people who have to enter a residential area. As long as one person has an access card, the guard will not check whether the other person has an access card and let him go.

In the above code, we see the submit method of the ExecutorCompletionService object used to submit the task, let's look at the submit method in the ExecutorCompletionService class, as shown below.

public Future<V> submit(Callable<V> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<V> f = newTaskFor(task);
    executor.execute(new QueueingFuture(f));
    return f;
}

public Future<V> submit(Runnable task, V result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<V> f = newTaskFor(task, result);
    executor.execute(new QueueingFuture(f));
    return f;
}

As you can see, the submit method in the ExecutorCompletionService class essentially calls the execute method of the Executor interface.

invokeAny method

public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
    throws InterruptedException, ExecutionException {
    try {
        return doInvokeAny(tasks, false, 0);
    } catch (TimeoutException cannotHappen) {
        assert false;
        return null;
    }
}

public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                       long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException {
    return doInvokeAny(tasks, true, unit.toNanos(timeout));
}

The two invokeAny methods are essentially calling the doInvokeAny method, submitting multiple tasks in the thread pool, and only returning one result data.

Looking directly at the above code, everyone may be a little dizzy. Here, let me give an example. When we use the thread pool, we may start multiple threads to perform their own tasks. For example, thread A is responsible for task_a and thread B is responsible for task_b. This can greatly increase the speed of system processing tasks. If we want one of the threads to return immediately when the execution is completed and the result data is returned, there is no need to let other threads continue to perform tasks. At this point, you can use the invokeAny method.

invokeAll method

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    throws InterruptedException {
    if (tasks == null)
        throw new NullPointerException();
    ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
    //标识所有任务是否完成
    boolean done = false;
    try {
        //遍历所有任务
        for (Callable<T> t : tasks) {
            将每个任务封装成RunnableFuture对象提交任务
            RunnableFuture<T> f = newTaskFor(t);
            //将结果数据添加到futures集合中
            futures.add(f);
            //执行任务
            execute(f);
        }
        //遍历结果数据集合
        for (int i = 0, size = futures.size(); i < size; i++) {
            Future<T> f = futures.get(i);
            //任务没有完成
            if (!f.isDone()) {
                try {
                    //阻塞等待任务完成并返回结果
                    f.get();
                } catch (CancellationException ignore) {
                } catch (ExecutionException ignore) {
                }
            }
        }
        //任务完成(不管是正常结束还是异常完成)
        done = true;
        //返回结果数据集合
        return futures;
    } finally {
        //如果发生中断异常InterruptedException 则取消已经提交的任务
        if (!done)
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
    }
}

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                     long timeout, TimeUnit unit)
    throws InterruptedException {
    if (tasks == null)
        throw new NullPointerException();
    long nanos = unit.toNanos(timeout);
    ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
    boolean done = false;
    try {
        for (Callable<T> t : tasks)
            futures.add(newTaskFor(t));

        final long deadline = System.nanoTime() + nanos;
        final int size = futures.size();

        for (int i = 0; i < size; i++) {
            execute((Runnable)futures.get(i));
            // 在添加执行任务时超时判断,如果超时则立刻返回futures集合
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L)
                return futures;
        }
         // 遍历所有任务
        for (int i = 0; i < size; i++) {
            Future<T> f = futures.get(i);
            if (!f.isDone()) {
                //对结果进行判断时进行超时判断
                if (nanos <= 0L)
                    return futures;
                try {
                    f.get(nanos, TimeUnit.NANOSECONDS);
                } catch (CancellationException ignore) {
                } catch (ExecutionException ignore) {
                } catch (TimeoutException toe) {
                    return futures;
                }
                //重置任务的超时时间
                nanos = deadline - System.nanoTime();
            }
        }
        done = true;
        return futures;
    } finally {
        if (!done)
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
    }
}

The invokeAll method also implements the logic of no timeout setting and timeout setting.

The overall logic of the invokeAll method without timeout setting is: encapsulate all tasks into RunnableFuture objects, call the execute method to execute the task, add the returned result data to the futures collection, and then traverse the futures collection to determine whether the task is complete, if not When completed, call the get method to block the task until the result data is returned, at which point the exception will be ignored. Finally, in the finally code block, the identification of whether all tasks are completed is judged, and if there are uncompleted tasks, the submitted tasks are cancelled.

The overall logic of the invokeAll method with a timeout setting is basically the same as the overall logic of the invokeAll method without a timeout setting, except that the logic judgment of the timeout is added in two places. One is to perform a timeout judgment when adding a task to execute, and if it times out, it will immediately return to the futures collection; the other is to add a timeout processing logic every time the result data is judged.

Essentially, the invokeAll method still calls the execute method of the Executor interface to submit the task.

submit method

The logic of the submit method is relatively simple, that is, the task is encapsulated into a RunnableFuture object and submitted, and the Future result data is returned after the task is executed. As follows.

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, result);
    execute(ftask);
    return ftask;
}

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

It can be seen from the source code that when the submit method submits a task, it is essentially the execute method of the Executor interface that is called.

To sum up, when a task is submitted in a thread pool of a non-timed task, it is essentially the execute method of the Executor interface that is called. As for the execute method of which specific implementation class is called, we will analyze it in depth in a later article.

Five, ScheduledExecutorService interface

The ScheduledExecutorService interface is derived from the ExecutorService interface, inherits all the functions of the ExecutorService interface, and provides the ability to process tasks regularly. The source code of the ScheduledExecutorService interface is relatively simple, as shown below.

package java.util.concurrent;

public interface ScheduledExecutorService extends ExecutorService {

    //延时delay时间来执行command任务,只执行一次
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);

    //延时delay时间来执行callable任务,只执行一次
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

    //延时initialDelay时间首次执行command任务,之后每隔period时间执行一次
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
 
    //延时initialDelay时间首次执行command任务,之后每延时delay时间执行一次
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

}

So far, we have analyzed the important top-level interfaces and abstract classes in the thread pool system.

Through the analysis of these top-level interfaces and abstract classes, we need to understand and experience the abstract thinking in software development, deeply understand the realization of abstract thinking in specific coding, and finally form our own programming thinking and apply it to actual projects. This is one of the many details we can learn from the source code. This is one of the reasons why senior or senior engineers and architects must understand the details of the source code.

Click to follow and learn about Huawei Cloud's fresh technology for the first time~

阅读 388

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态...

1.1k 声望
1.7k 粉丝
0 条评论
你知道吗?

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态...

1.1k 声望
1.7k 粉丝
文章目录
宣传栏