漫谈JAVA之Executor框架(1)

序言

从JDK5开始,把工作单元与执行机制分离开来,工作单元包括Runnable和Callable,而执行机制由接下来要讲的Executor框架提供。java.util.concurrent.Executorjava.util.concurrent.ExecutorService java.util.concurrent.Executors 这三者均是JAVA Executor框架的一部分,用来提供线程池的功能。因为创建于销毁线程消耗太多计算机资源,而且操作系统通常对线程数有限制,所以建议使用线程池来管理线程和并发执行任务,这样不用每次请求过来时就创建一个线程。从线程池免去创建销毁线程的资源(包括时间)损耗,利用这点提高了应用的响应处理速度,还可避免"java.lang.OutOfMemoryError:unable to create new native thread"之类的错误。

Executor框架简介

1.1 Executor框架的两级调度模型
根据《JAVA 并发编程的艺术》中了解到,在HotSpot VM的线程模型中,Java线程(java.lang.Thread)被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当该Java线程终止时,对应操作系统线程会被回收。而CPU资源是有限的,线程数量也会被限制,由操作系统调度分配相应的CPU资源。从图中可以看出,在上层中,在任务通过Executor框架分配到线程池中的线程,而底层,操作系统内核将这些线程映射到硬件处理器上。

任务两级调度模型.jpg

2.2 Executor

Executor、ExecutorService和Executors 最主要的区别是 Executor是一个抽象层的核心接口(大致代码和UML类图如下)

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}
Executor接口 类图.jpg

不同于 java.lang.Thread类将任务和执行耦合在一起,Executor将任务本身和执行任务分离,可以阅读 difference between Thread and Executor 了解更多Thread和Executor的不同

2.3 ExecutorService

ExecutorService接口 对Executor接口的子接口,增加了一些常见的对线程的控制方法,如提供提交线程任务,返回Future对象,终止、关闭线程池等方法。当调用shutdown方法时,线程池会停止接受新任务,但会完成正在pending中的任务。

Future对象提供了异步执行,意味着无需等待任务执行完成,提交需要执行的任务后,它立即返回继续往下执行 ,然后在需要时检查Future是否有结果了,如果任务已执行完毕,通过Future.get()方法获取执行结果,因为Future.get()是阻塞方法,如果考虑可能会因不明原因而导致Future.get()方法阻塞下去,Future还提供了设置获取超时方法Future.get(long timeout, TimeUnit unit)方法。

通过ExecutorService.submit()方法返回的Future对象,还可以取消任务的执行。Future提供了cancel方法用于取消正在执行pending中的任务。
ExecutorService 部分代码如下

public interface ExecutorService extends Executor {
    void shutdown();
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
    
}

2.4 Executors

Executors 是一个工具类,类似于Collections。 提供工厂方法来创建不同类型的线程池,例如FixedThreadPool或者CacheThreadPool

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

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

}


下面详细看一下三者区别:

  • Executor 和 ExecutorService 这两个接口主要的区别是:ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口

  • Executor 和 ExecutorService 第二个区别是:Executor 接口定义了 execute()方法用来接收一个Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受RunnableCallable接口的对象。

  • Executor 和 ExecutorService 接口第三个区别是 Executor 中的 execute() 方法不返回任何结果,而 ExecutorService 中的 submit()方法可以通过一个 Future 对象返回运算结果。

  • Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。可以通过 《Java Concurrency in Practice》 一书了解更多关于关闭线程池和如何处理 pending 的任务的知识。

  • Executors 类提供工厂方法用来创建不同类型的线程池。比如: newSingleThreadExecutor() 创建一个只有一个线程的线程池,newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池,newCachedThreadPool()可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。

2.5 Executor框架的成员

Executor框架的主要成员:ThreadPoolExecutor、ScheduledThreadPoolExecutor、Future接口、Runnable接口、Callable接口和Executors工具类。这个就不细说,下一讲,重点讲下ThreadPoolExecutor。

参考:

  1. Executor, ExecutorService 和 Executors 间的不同

  2. JAVA并发编程的艺术

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容