Java concurrent programming (3): Executor framework

1. The structure and members of the Executor framework

In the thread two-level scheduling model, Java threads are mapped one-to-one to native operating system threads. A native operating system thread is created when a Java thread starts; when the Java thread terminates, the operating system thread is also recycled. The operating system schedules all threads and assigns them to the available CPUs.

At the upper layer, Java multithreaded programs usually decompose the application into several tasks, and then use the user-level scheduler (Executor framework) to map these tasks into a fixed number of threads; at the bottom layer, the operating system kernel maps these threads to hardware processing on the device.

unnamed file.png

1.1, the structure of the Executor framework

The Executor framework is mainly composed of three parts:

  • Task : Include the interface that the executed task needs to implement: Runnable interface or Callable interface.

  • Task execution : including the core interface Executor of the task execution mechanism, and the ExecutorService interface inherited from Executor. The Executor framework has two key classes that implement the ExecutorService interface (ThreadPoolExecutor and ScheduledThreadPoolExecutor).

  • The result of asynchronous calculation : including the interface Future and the FutureTask class that implements the Future interface.

The main classes and interfaces included in the Executor framework:

  • Executor is an interface that is the basis of the Executor framework, which separates the submission of tasks from the execution of tasks.

  • ThreadPoolExecutor is the core implementation class of the thread pool, which is used to execute the submitted tasks.

  • ScheduledThreadPoolExecutor is an implementation class that can run commands after a given delay, or execute commands periodically. ScheduledThreadPoolExecutor is more flexible and powerful than Timer.

  • The Future interface and the FutureTask class that implements the Future interface represent the results of asynchronous computations.

  • Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或Scheduled-ThreadPoolExecutor执行。

Executor框架的使用示意图:

unnamed file.png

  • 主线程首先要创建实现Runnable或者Callable接口的任务对象。工具类Executors可以把一个Runnable对象封装为一个Callable对象(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule))。

  • 然后可以把Runnable对象直接交给ExecutorService执行(ExecutorService.execute(Runnable command));或者也可以把Runnable对象或Callable对象提交给ExecutorService执行(Executor-Service.submit(Runnable task)或ExecutorService.submit(Callabletask))。

  • 如果执行ExecutorService.submit(…),ExecutorService将返回一个实现Future接口的对象。

  • 最后,主线程可以执行future.get()方法来等待任务执行完成。主线程也可以执行future.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

1.2、Executor框架的成员

Executor框架的主要成员:ThreadPoolExecutor、ScheduledThreadPoolExecutor、Future接口、Runnable接口、Callable接口和Executors。

1、ThreadPoolExecutor:

ThreadPoolExecutor通常使用工厂类Executors来创建。

Executors可以创建3种类型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool和CachedThreadPool。

  • FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactorythreadFactory)

复制代码

创建使用固定线程数的FixedThreadPool的API。 FixedThreadPool适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。

  • SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
复制代码

创建使用单个线程的SingleThreadExecutor的API。SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。

  • CachedThreadPool
public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

复制代码

创建一个会根据需要创建新线程的CachedThreadPool的API。 CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。

2、ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor通常使用工厂类Executors来创建。Executors可以创建2种类型的ScheduledThreadPoolExecutor:

  • ScheduledThreadPoolExecutor:包含若干个线程的ScheduledThreadPoolExecutor。

  • SingleThreadScheduledExecutor:只包含一个线程的ScheduledThreadPoolExecutor。

创建固定个数线程的ScheduledThreadPoolExecutor的API。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize,ThreadFactory threadFactory)

复制代码

ScheduledThreadPoolExecutor适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。

创建单个线程的SingleThreadScheduledExecutor的API。

public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor
(ThreadFactory threadFactory)
复制代码

SingleThreadScheduledExecutor适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。

3、Future接口

Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。当我们把Runnable接口或Callable接口的实现类提交(submit)给ThreadPoolExecutor或ScheduledThreadPoolExecutor时,ThreadPoolExecutor或ScheduledThreadPoolExecutor会向我们返回一个FutureTask对象。

<T> Future<T> submit(Callable<T> task)
<T> Future<T> submit(Runnable task, T result)
Future<> submit(Runnable task)
复制代码

4、Runnable接口和Callable接口

Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。它们之间的区别是Runnable不会返回结果,而Callable可以返回结果。

除了可以自己创建实现Callable接口的对象外,还可以使用工厂类Executors来把一个Runnable包装成一个Callable。

下面是Executors提供的,把一个Runnable包装成一个Callable的API。

// 假设返回对象Callable1
public static Callable<Object> callable(Runnable task)        
复制代码

2、ThreadPoolExecutor详解

2.1、FixedThreadPool详解

FixedThreadPool被称为可重用固定线程数的线程池。下面是FixedThreadPool的源代码实现。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
复制代码

FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。

当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。这里把keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止。

执行流程:

  • 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。

  • 在线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入LinkedBlockingQueue。

  • 线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。

FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响。

  • 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。

  • 使用无界队列时maximumPoolSize将是一个无效参数。

  • 使用无界队列时keepAliveTime将是一个无效参数。

  • 由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)。

2.2、SingleThreadExecutor详解

SingleThreadExecutor是使用单个worker线程的Executor。下面是SingleThreadExecutor的源代码实现。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
复制代码

SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1。其他参数与FixedThreadPool相同。SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。SingleThreadExecutor使用无界队列作为工作队列对线程池带来的影响与FixedThreadPool相同。

执行流程:

  • 如果当前运行的线程数少于corePoolSize(即线程池中无运行的线程),则创建一个新线程来执行任务。

  • 在线程池完成预热之后(当前线程池中有一个运行的线程),将任务加入LinkedBlockingQueue。

  • 线程执行完1中的任务后,会在一个无限循环中反复从LinkedBlockingQueue获取任务来执行。

2.3、CachedThreadPool详解

CachedThreadPool是一个会根据需要创建新线程的线程池。下面是创建CachedThread-Pool的源代码。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
复制代码

CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为Integer.MAX_VALUE,即maximumPool是无界的。这里把keepAliveTime设置为60L,意味着CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。

CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。

执行流程:

  • 首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成;否则执行下面的步骤2。

  • 当初始maximumPool为空,或者maximumPool中当前没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤1将失败。此时CachedThreadPool会创建一个新线程执行任务,execute()方法执行完成。

  • 在步骤2中新创建的线程将任务执行完后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒钟。如果60秒钟内主线程提交了一个新任务(主线程执行步骤1),那么这个空闲线程将执行主线程提交的新任务;否则,这个空闲线程将终止。由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的CachedThreadPool不会使用任何资源。

SynchronousQueue是一个没有容量的阻塞队列。每个插入操作必须等待另一个线程的对应移除操作,反之亦然。CachedThreadPool使用SynchronousQueue,把主线程提交的任务传递给空闲线程执行。CachedThreadPool中任务传递的示意图:

unnamed file.png

3、ScheduledThreadPoolExecutor详解

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务

3.1、ScheduledThreadPoolExecutor的运行机制

部分源码如下:

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}
复制代码

DelayQueue是一个无界队列,所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中没有什么意义。

执行流程:

  • 当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFutur接口的ScheduledFutureTask。

  • 线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    if (period <= 0L)
        throw new IllegalArgumentException();
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(period),
                                      sequencer.getAndIncrement());
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    delayedExecute(t);
    return t;
}
复制代码

ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下的修改。

  • 使用DelayQueue作为任务队列。

  • 获取任务的方式不同

  • 执行周期任务后,增加了额外的处理。

3.2、ScheduledThreadPoolExecutor的实现

前面提到过,ScheduledThreadPoolExecutor会把待调度的任务(ScheduledFutureTask)放到一个DelayQueue中。

ScheduledFutureTask主要包含3个成员变量:

  • long型成员变量time,表示这个任务将要被执行的具体时间。

  • long型成员变量sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor中的序号。

  • long型成员变量period,表示任务执行的间隔周期。

DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的Scheduled-FutureTask进行排序。排序时,time小的排在前面(时间早的任务将被先执行)。如果两个ScheduledFutureTask的time相同,就比较sequenceNumber,sequenceNumber小的排在前面(也就是说,如果两个任务的执行时间相同,那么先提交的任务将被先执行)。

unnamed file.png 执行流程:

  • 线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time大于等于当前时间。

  • 线程1执行这个ScheduledFutureTask。

  • 线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间。

  • 线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。

下面是DelayQueue.take()方法的源代码实现。

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();                      // 1
    try {
        for (;;) {
            E first = q.peek();
            if (first == null) {
                available.await();                   // 2.1
            } else {
                long delay =  first.getDelay(TimeUnit.NANOSECONDS);
                if (delay > 0) {
                    long tl = available.awaitNanos(delay);   // 2.2
                } else {
                    E x = q.poll();                  // 2.3.1
                    assert x != null;
                    if (q.size() != 0)
                        available.signalAll();         // 2.3.2
                    return x;
                }
            }
        }
    } finally {
        lock.unlock();                            // 3
    }
}
复制代码

unnamed file.png 获取任务执行流程:

  • 获取Lock。

  • 获取周期任务。

    • 如果PriorityQueue为空,当前线程到Condition中等待;否则执行下面的2.2。

    • 如果PriorityQueue的头元素的time时间比当前时间大,到Condition中等待到time时间;否则执行下面的2.3。

    • 获取PriorityQueue的头元素(2.3.1);如果PriorityQueue不为空,则唤醒在Condition中等待的所有线程(2.3.2)。

  • 释放Lock。

ScheduledThreadPoolExecutor在一个循环中执行步骤2,直到线程从PriorityQueue获取到一个元素之后(执行2.3.1之后),才会退出无限循环(结束步骤2)。

最后,让我们看看ScheduledThreadPoolExecutor中的线程执行任务的步骤4,把ScheduledFutureTask放入DelayQueue中的过程。下面是DelayQueue.add()的源代码实现。

public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();                // 1
    try {
        E first = q.peek();
        q.offer(e);              // 2.1
        if (first == null || e.compareTo(first) < 0)
            available.signalAll();    // 2.2
        return true;
    } finally {
        lock.unlock();           // 3
    }
}

复制代码

ScheduledFutureTask放入DelayQueue中的过程:

  • 获取Lock。

  • 添加任务。

    • 向PriorityQueue添加任务。

    • 如果在上面2.1中添加的任务是PriorityQueue的头元素,唤醒在Condition中等待的所有线程。

  • 释放Lock。

4、FutureTask详解

4.1、FutureTask简介

In addition to implementing the Future interface, FutureTask also implements the Runnable interface. Therefore, the FutureTask can be handed over to the Executor for execution, or it can be executed directly by the calling thread (FutureTask.run()). Depending on when the FutureTask.run() method is executed, the FutureTask can be in the following three states:

  • have not started. The FutureTask is in an unstarted state before the FutureTask.run() method is executed. When a FutureTask is created and the FutureTask.run() method is not executed, the FutureTask is not started.

  • activated. During the execution of the FutureTask.run() method, the FutureTask is in the started state.

  • completed. The FutureTask.run() method ends normally after execution, or is canceled (FutureTask.cancel(…)), or an exception is thrown when the FutureTask.run() method is executed and ends abnormally, and the FutureTask is in the completed state.

When the FutureTask is not started or started, executing the FutureTask.get() method will cause the calling thread to block; when the FutureTask is in the completed state, executing the FutureTask.get() method will cause the calling thread to return the result immediately or throw an exception .

When the FutureTask is not started, executing the FutureTask.cancel() method will cause the task to never be executed; when the FutureTask is in the started state, executing the FutureTask.cancel(true) method will interrupt the thread executing the task. To try to stop the task; when the FutureTask is in the started state, executing the FutureTask.cancel(false) method will have no effect on the thread that is executing the task (let the executing task run to completion); when the FutureTask is in the completed state , executing the FutureTask.cancel(…) method will return false.

unnamed file.png

Guess you like

Origin juejin.im/post/7086749464081203208