2
头图

Hello everyone, I'm Glacier~~

In the previous article " [High Concurrency] An Analysis of Thread Pools and ThreadPoolExecutor Classes That I Have to Say ", I introduced Java's thread pools as a whole. If you carefully taste the underlying source code implementation of the thread pool, you will find that the design of the entire thread pool system is very elegant! The design of these codes is worthy of our careful taste and study, from which we can learn the design specifications of elegant codes, and form our own design ideas for my use! Haha, that's a lot. Next, let's take a look at the very important interfaces and abstract classes in the thread pool, and deeply analyze how the idea of abstraction is fully used in the thread pool!

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

Digression: I worship Doug Lea, the great god of Java. The concurrent package in Java is written by this old man. He is the person who has the most influence on Java in the world.

An 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 following figure.

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 with no return value.
  • ExecutorService interface: derived from the Executor interface, which extends many functions, such as closing the thread pool, submitting tasks and returning result data, and waking up tasks in the thread pool.
  • AbstractExecutorService abstract class: derived from the ExecutorService interface, implements several very implemented methods for subclasses to call.
  • The ScheduledExecutorService timed task interface, derived from the ExecutorService interface, has all the methods defined by the ExecutorService interface, and extends the methods related to timed tasks.

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

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

3. ExecutorService interface

The ExecutorService interface is the core interface of the thread pool of the non-timed task class. Through the ExecutorService interface, you can submit tasks to the thread pool (both with and without return 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, you can directly comment in the source code of the above interface. These interface methods are relatively simple, so I will not repeat the descriptions 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 which several more practical methods are implemented and provided to subclasses for invocation. 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 AbstractExecutorService and 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 to execute the tasks of the thread pool in batches and finally return a result data. Through the analysis of the source code, we can find that as long as this method obtains a result data, all running tasks in the thread pool will be canceled, and Return the result data. This is like a lot of people who want to enter a residential area. As long as one person has an access control card, the guard will no longer check whether other people have access control cards, and let them go directly.

In the above code, we see the submit method of the ExecutorCompletionService object used for submitting tasks. 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;
}

It can be seen that 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));
}

These two invokeAny methods are essentially calling the doInvokeAny method to submit multiple tasks in the thread pool, as long as one result data is returned.

Looking directly at the above code, you 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 respective tasks. For example, thread A is responsible for task_a, and thread B is responsible for task_b, which can greatly improve the speed of the system processing tasks. If we want one of the threads to return immediately when it finishes returning the result data, we don't need to let other threads continue to execute 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 completed, if not After completion, call the get method to block the task until the result data is returned, at which time the exception will be ignored. Finally, in the finally code block, the flags of whether all tasks are completed are judged. If there are unfinished tasks, the submitted tasks are cancelled.

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

The invokeAll method essentially 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, encapsulate the task into a RunnableFuture object and submit it, and return the Future result data after executing the task. 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 class, it is essentially the execute method of the Executor interface that is called . As for which execute method of the 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 comprehend and experience the abstract thinking in software development, deeply understand the implementation 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 know the details of source code.

Okay, let's stop here today, I'm Glacier, see you next time~~


冰河
156 声望970 粉丝