Java基础 异步任务与Executor框架

1. 引言

同步任务的发起和执行是在同一条时间线上进行的,往往以为的阻塞,而异步任务的发起和执行在不同的时间线上。但是阻塞/非阻塞与同步/异步执行方式并没有完全的对应关系,同步/异步的说法也是相对的,他取决于任务的执行方式以及我们的观察角度。

2. Java Executor框架

2.1 Runnable、Callable接口

Runnable和Callable接口都是对任务处理逻辑对抽象,使得我们无需关注任务的具体处理逻辑。

//没有返回状态
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

//有返回状态
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

2.2 Executor接口

Executor接口使得任务的提交能够与任务执行的具体细节解耦,能够给我们带来信息隐藏和关注点分离的好处。

public interface Executor {

    /**
     * 传入的Runnable实例将被执行,具体是在一个新的线程,或者线程池,
     * 或者与被调用者在同一线程,取决于Executor接口的具体实现
     */
    void execute(Runnable command);
}

Executor接口我们可以发现两个问题:

  • 无法将任务处理结果返回给客户端
  • 没有定义释放资源的方法

2.3 ExecutorService接口

ExecutorService接口继承自Executor接口,ThreadPoolExecutor是其默认实现,相对于上面提到的两点问题:

  • 其定义了几个submit方法,这些方法能够接受Callable接口或者Runnable接口表示的任务,并返回相应的Future实例,从而使客户端在提交任务后可以获取任务执行结果
  • 定义了shutdown()方法和shutdownNow()方法来释放资源

下面列举了一部分ExecutorService接口定义的方法

public interface ExecutorService extends Executor {

    // 提交一个callable实例,可以通过返回的future对象获取到返回结果
    <T> Future<T> submit(Callable<T> task);

    // 提交一个runnabel实例,执行成功返回的future get()方法返回null
    Future<?> submit(Runnable task);

    //阻塞方法,提交callable实例集合,当第一个执行成功后便返回结果,并终止其余线程执行
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    // 阻塞方法,提交callable实例集合,当所有执行成功后返回future实例集合
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    // 正在执行的任务会继续执行下去,没有被执行的则中断
    void shutdown();

    // 正在执行的任务停止,返回没被执行的任务
    List<Runnable> shutdownNow();

    boolean isShutdown();

    boolean isTerminated();
}

2.4 Executors实用工具类

Excecutors能够返回默认线程工厂,将Runnable实例转为Callable实例,并提供了一些能够返回ExecutorService实例的快捷方法。

方法 说明
public static ExecutorService newCachedThreadPool() 适用于执行大量耗时较短且提交比较频繁的任务。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) 同上
public static ExecutorService newFixedThreadPool(int nThreads) 线程池大小等于最大线程池大小,该线程池中的工作者线程一般不会超时
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 同上
public static ExecutorService newSingleThreadExecutor() 适合单/多生产者——单消费者模式
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) 同上
  • newCachedThreadPool()
    相当于new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    60L, TimeUnit.SECONDS,
    new SynchronousQueue());
    核心线程池大小为0,最大线程池大小不受限,工作者线程允许的最大空闲时间为60s,内部以SynchronousQueue为工作队列,但是SynchronousQueue内部并不维护用于存储队列元素的实际存储空间。在极端情况下,给该线程池每提交一个任务都会导致一个新的工作者线程被创建,因此适合适用于执行大量耗时较短且提交比较频繁的任务。
  • public static ExecutorService newFixedThreadPool(int nThreads)
    相当于new ThreadPoolExecutor(nThreads, nThreads,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue());
    核心线程池大小与最大线程池大小均为nThreads,线程池中的空闲工作者线程不会被自动清理,注意使用完后需要主动关闭。

2.5 Future与FutureTask

无论是Runnable实例还是Callable实例所表示的任务,只要我们将其提交给线程池执行,那么这些任务就是异步任务。

  • Runnable实例的优点是可以使用单独的工作线程执行,缺点是没有返回值
  • Callable实例的优点是有返回值,但是缺点是只能用线程池执行,灵活性较低。

首先最基本的接口是Future,他代表了异步任务执行之后的返回信息

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    //阻塞方法,等任务执行完成后返回结果
    V get() throws InterruptedException, executionException;

    V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;

}

FutureTask是Future接口的基本实现类,他融合了Runnable接口(继承)和Callable接口(组合)的优点,他的构造器可以将Callable实例转换为Runnable实例,同时也可以将FutureTask实例交给Executor执行。

发布了86 篇原创文章 · 获赞 25 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章