# Executor 框架

Java 线程创建和销毁需要一定开销,池化技术避免了频繁的创建和销毁,通过向线程池传入任务重复利用线程。Java 的线程既是工作单元,也是执行机制。JDK1.5 开始将工作单元和执行机制分开,工作单元是 RunnableCallable ,执行机制由 Executor 框架提供。

Executor 框架主要由三部分组成:

  • 任务:任务需要实现 RunnableCallable
  • 任务的执行: ExecutorExecutorService 接口。
  • 异步计算的结果:接口 Future 和实现该接口的 FutureTask

Executor 框架形成的两级调度模型,上层,Java 多线程程序将应用分解为多个任务,使用用户级的调度器( Executor 框架)将任务映射为固定数量的线程;底层,操作系统将线程映射到硬件处理器上。

整个执行流程如下:

主线程创建实现 RunnableCallable 接口的任务对象。 Executors 工具类可以把 Runnable 封装成 Callable 对象( Executors.callable(Runnable task)Executors.callable(Runnable task, Object resule) )。然后将任务对象交给 ExecutorService 执行( ExecutorService.submit(Runnable task/Callable<T) task )。 ExecutorService 会返回一个实现 Future 接口的对象(JDK8 是 FutureTask 对象)。最后主线程执行 FutureTask.get() 等待任务执行完成。

# Executor

public interface Executor {
    void execute(Runnable command);
}

Executor 接口提供一种将任务提交从任务运行分离开来的方法。通常使用 Executor 而不是显式地创建线程。

一般来说,任务提交给 Excutor ,任务不会在提交线程中运行,而是交给其他线程

class ThreadPerTaskExecutor implements Executor {
    public void execute(Runnable r) {
        new Thread(r).start();
    }
}

但是 Excutor 也不是严格异步,最简单的情况下, Excutor 可以在调用者线程中立即运行提交的任务

class DirectExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}

其实就是看实现类如何编写代码。Executor 的主要作用:提供了一种显示创建线程的方式。

  • JDK1.5 之前的方式: Thread + Runnable
  • Executor 提供的的方式: Executor + Runnable

相关类结构(理清结构非常重要)

# ExecutorService

ExecutorService 继承自 Executor 接口, ExecutorService 提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以关闭 ExecutorService ,这将导致其停止接受新任务。关闭后,执行程序将最后终止,这时没有任务在执行,也没有任务在等待执行,并且无法提交新任务。

详细讲解就是:

  • ExecutorService 接口是一个执行器,它可以终止任务。
  • ExecutorService 接口还提供了返回 Future 接口的方法,这个方法可以用于追踪一个或多个异步任务的执行情况。
  • ExecutorService 可以手动关闭,这种操作会导致它拒绝新的任务。
// 不再接受新任务,允许之前已经提交的方法执行完毕,然后再关闭执行器
shutdown();
// 阻止正在等待的任务开启,并且会试图停止正在执行的任务,然后关闭执行器。
shutdownNow();
// 提交方法
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);	
<T> Future<T> submit(Runnable task, T result);

从类关系图可以看出, ExecutorService 扩展了 Executor 的功能,提供的功能逐渐向线程池靠近

public void f() {
    //newCachedThreadPool 就是一个线程池
    ExecutorService executorService = Executors.newCachedThreadPool();
    
    // ExecutorService 通过 submit 提交任务
    executorService.submit(() -> {
        System.out.println("--- ExecutorService begin.");
        try {
            Thread.sleep(1000);
            System.out.println("--- ExecutorService end.");
        } catch (InterruptedException e) {
            System.out.println("--- ExecutorService is interrupted.");
        }
    });
    executorService.shutdown();
}

其他重要方法

// 在线程池服务执行 shutdown () 或者 shutdownNow () 方法之后,所有的任务是否已经完成
// 如果没有执行 shutdown () 或者 shutdownNow () 方法,则永远返回 false
executorService.isTerminated();
// 阻塞等待所有的任务终止
// 如果等待时间超时,则返回 false
// 如果当前线程被 interrupt,则抛出 InterruptedException 异常
// 如果线程池了执行 shutdown () 或者 shutdownNow () 方法,并且所有的任务都已经完成,则返回 true
// 如果线程池未执行 shutdown () 或者 shutdownNow () 方法,则永远返回 false
executorService.awaitTermination(1000, TimeUnit.MILLISECONDS);

# AbstractExecutorService

AbstractExecutorService 继承自 ExecutorService 接口,其提供 ExecutorService 执行方法的默认实现。此类使用 newTaskFor() 返回的 RunnableFuture 实现 submitinvokeAnyinvokeAll 方法,默认情况下, RunnableFuture 是此包中提供的 FutureTask 类。

invokeAny() 是调用 newTaskFor () 方法对任务进行了包装为 RunnableFuture 对象,然后调用了本对象的 execute () 方法提交任务,并返回异步计算结果对象。

# ScheduledExecutorService

Scheduled 可以看出与定时有关。可以在给定延时之后调度任务,也可以根据指定的周期调度任务。

  • schedule () 方法可以创建含有延时 (delays) 变量的任务,然后返回一个可以用于取消检查运行状态 Future 对象。如果 delays<=0 ,则会立即执行。
  • scheduleAtFixedRate () 方法 scheduleWithFixedDelay () 方法可以创建并运行定期运行的任务。

详细方法说明:

1. schedule(Runnable command,long delay, TimeUnit unit)

  • 在一定延时 (delay) 之后,运行 Runnable 任务。
  • 此任务只运行一次。

2. schedule(Callable callable,long delay, TimeUnit unit)

  • 在一定延时 (delay) 之后,运行 Callable 任务。
  • 此任务只运行一次。

3. scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)

  • 在一定延时 (initialDelay) 之后,开始周期性的运行 Runnable 任务。
  • 周期性:上一次任务执行完成之后,等待一段时间 (delay),然后开始下一次任务。

4. scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)

  • 在一定延时 (initialDelay) 之后,开始周期性的运行 Runnable 任务。
  • 周期性:每过一段时间 (period),就开始运行一次 Runnable 任务。
  • 如果任务的执行时间大于等待周期 (period):上一次任务执行完成之后,立即开始下一次任务。也就是说:每过一段时间 (任务执行时间),就开始运行一次 Runnable 任务。

# 参考

https://pdai.tech/md/java/thread/java-thread-x-juc-overview.html

https://blog.csdn.net/hanchao5272/article/details/79829407

https://blog.csdn.net/hanchao5272/article/details/79830245

https://blog.csdn.net/hanchao5272/article/details/79834744

https://www.jianshu.com/p/7418bedd520f

《并发编程的艺术》第 10 章