ThreadPool,Executor,Service以及FutureTask在并行策略中的层级与作用

77 阅读5分钟

ThreadPool,Executor,Service以及FutureTask在并行策略中的层级与作用

Executor

先来看看Executor接口的解释:

public interface Executor {
    /**
    *在未来某个时间执行给定命令。命令可以在新线程、池线程或调用线程中执行,由Executor实现决定。
    *形参:
    *命令--可运行的任务
    *抛出:
    *RejectedExecutionException- 如果该任务无法被接受执行
    *NullPointerException- 如果命令为空
    */
    void execute(Runnable command);
}

也就是说,Executor只规定了可以接收任务,任务可能会被执行。以下是可以自由实现的点:

  • 任务被执行的时间
  • 执行任务的线程

执行已提交的Runnable任务的对象。该接口提供了一种将任务提交与每个任务如何运行的机制(包括线程使用、调度等细节)分离开来的方法。执行器通常用来代替显式创建线程。例如,你可以为一组任务中的每个任务调用new Thread(new RunnableTask()).start()而不是执行器:

Executor executor = anExecutor();

executor.execute(new RunnableTask1());

executor.execute(new RunnableTask2());

...

不过, Executor接口并不严格要求执行必须是异步的。在最简单的情况下,执行器可以在调用者的线程中立即运行提交的任务:

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

许多 执行器实现都会对任务调度的方式和时间施加某种限制。下面的执行器将任务提交序列化到第二个执行器,说明了一个复合执行器。

class SerialExecutor implements Executor {
    final Queue<Runnable> tasks = new ArrayDeque<>();
    final Executor executor;
    Runnable active;

    SerialExecutor(Executor executor) {
        this.executor = executor;
    }

    public synchronized void execute(Runnable r) {
        tasks.add(() -> {
            try {
                r.run();
            } finally {
                scheduleNext();
            }
        });
        if (active == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
        if ((active = tasks.poll()) != null) {
            executor.execute(active);
        }
    }
}

Future

Future表示异步计算的结果。提供的方法用于检查计算是否完成、等待计算完成以及检索计算结果。只有在计算完成后才能使用get方法获取结果,必要时会阻塞,直到计算准备就绪。取消计算由cancel方法执行。还提供了其他方法来确定任务是正常完成还是被取消。计算一旦完成,就不能取消。

如果想使用Future来实现可取消性,但又不想提供可用的结果,可以声明形式为Future<?> 的类型,并返回空作为底层任务的结果。

RunnableFuture

同时实现了Runnable和Future接口。可运行的 Future。运行方法的成功执行会导致Future 的完成,并允许访问其结果。

FutureTask

经典的RunnableFuture实现。可取消的异步计算。该类提供了Future 的基本实现,包含启动和取消计算、查询计算是否完成以及检索计算结果的方法。只有在计算完成后才能获取结果;如果计算尚未完成,获取方法将阻塞。一旦计算完成,就不能重新启动或取消计算(除非使用runAndReset 调用计算)。 FutureTask可用于封装Callable或Runnable对象。由于FutureTask实现了Runnable,因此FutureTask可以提交给Executor 执行。

FutureTask使用了状态模式进行设计。该任务的运行状态,最初为新状态。只有在 set、setException 和 cancel 方法中,运行状态才会过渡到结束状态。在完成过程中,状态的短暂值可能是 COMPLETING(正在设置结果)或 INTERRUPTING(仅在中断运行程序以满足 cancel(true) 时)。从这些中间状态到最终状态的转换使用更便宜的有序/快速写入,因为值是唯一的,不能进一步修改。可能的状态转换:

  • 新状态 -> 完成状态 -> 正常状态
  • 新状态 -> 完成状态 -> 异常状态
  • 新状态 -> 取消状态
  • 新状态 -> 中断状态 -> 被中断状态

影响状态的方法只有以下两个:

cancel

直接取消掉任务。根据mayInterruptIfRunning的值:

  • True:该任务状态会被尝试中断(此时短暂地进入中断中状态),在通知实际运行线程终端后进入已中断状态
  • False:直接进入已取消状态,不会干扰运行线程的行为。
run

由当前线程直接开始运行,运行完成后调用set方法,给这个任务设定一个结果值,前提是任务状态必须为NEW新状态。随后,任务状态会被短暂设定为COMPLETING完成中,设置好结果后转为NORMAL正常状态

不难看出,仅凭借FutureTask,你只能在当前线程执行任务。

ExecutorService

Executor存在以下不足:

  • 执行任务后无法查询到任务的进度,除非你专门为此编写了逻辑。
  • Executor无法被关闭。

ExecutorService是对Executor的扩展。为了解决第一个问题,也就是执行任务后无法查询到任务的进度,ExecutorService增加了<T> Future<T> submit(Callable<T> task)方法来代替execute()方法。submit()的调用者可以获得一个Future,调用者可以随时通过Future查看任务进度。除此之外还有批量提交任务的invokeAll()方法等等。

当我们将任务提交到Executor后,通常需要将任务储存起来。除此之外,为了执行这些任务,Executor可能还需要管理一些线程。这样一来,Executor只要存在,就不可避免的会有不小的性能损耗。我们需要在Executor不再被需要时关闭它的手段,而且这个手段还不能太过强硬。ExecutorService通过增加void shutdown();,List<Runnable> shutdownNow();等方法实现了这一点。

AbstractExecutorService

这是一个抽象类。AbstractExecutorService是ExecutorService的部分默认实现。

AbstractExecutorService的实现细节:

  • 传入AbstractExecutorService的任务会在其内部被封装为FutureTask,并返回Future,随后便会调用execute方法。
  • 最重要的execute方法的实现细节依然为空。

ThreadPoolExecutor

ThreadPoolExecutor继承了AbstractExecutorService,整个类围绕execute方法逻辑搭建。ThreadPoolExecutor类中通过线程池的思想尽可能复用线程来执行代码,这是一个相当庞大且健全的设计。本文旨在讨论ThreadPoolExecutor类上层的接口,抽象类的职责关系,所以ThreadPoolExecutor相关内容不再讨论,而且实在是太多了。我会在另一篇文章中详细讲解ThreadPoolExecutor。