Executor框架、ThreadPoolExecutor、3种常见的线程池

参考:https://blog.****.net/javazejian/article/details/50890554

1. Executor框架

为什么需要线程池?

在java中,使用线程来执行异步任务时,线程的创建和销毁需要一定的开销,如果我们为每一个任务创建一个新的线程来执行的话,那么这些线程的创建与销毁将消耗大量的计算资源。

同时为每一个任务创建一个新线程来执行,这样的方式可能会使处于高负荷状态的应用最终崩溃。

所以线程池的出现为解决这个问题带来曙光。

我们将在线程池中创建若干条线程,当有任务需要执行时就从该线程池中获取一条线程来执行任务,如果一时间任务过多,超出线程池的线程数量,那么后面的线程任务就进入一个等待队列进行等待,直到线程池有线程处于空闲时才从等待队列获取要执行的任务进行处理,以此循环.....这样就大大减少了线程创建和销毁的开销,也会缓解我们的应用处于超负荷时的情况。

1.1 Executor框架的两级调度模型

在java线程启动时会创建一个本地操作系统线程,当该java线程终止时,这个操作系统线程也会被回收。

而每一个java线程都会被一对一映射为本地操作系统的线程,操作系统会调度所有的线程并将它们分别给可用的CPU。

而所谓的映射方式是这样实现的:

  • 在上层,java多线程程序通过把应用分为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;
  • 在底层,操作系统内核将这些线程映射到硬件处理器上。这样种两级调度模型如下图所示:

Executor框架、ThreadPoolExecutor、3种常见的线程池

从图中我们可以看出,应用程序通过Executor框架控制上层的调度,而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。

1.2 Executor框架的结构

Executor框架的结构主要包括3个部分

  1. 任务:包括被执行任务需要实现的接口:Runnable接口或Callable接口
  2. 任务的执行:包括任务执行机制的核心接口Executor,以及继承自Executor的EexcutorService接口。Exrcutor有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)
  3. 异步计算的结果:包括接口Future和实现Future接口的FutureTask类

这些类间的关系的UML图:

Executor框架、ThreadPoolExecutor、3种常见的线程池

  • Extecutor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。
  • ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
  • ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。
  • ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。
  • Future接口和实现Future接口的FutureTask类,代表异步计算的结果。
  • Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或者ScheduledThreadPoolExecutor执行。区别就是Runnable无法返回执行结果,而Callable可以返回执行结果。
  • Executor 和 ExectorService 这两个接口的主要区别:
    • ExectorService 接口继承了Executor 接口,是它的子接口;
    • Executor 接口定义了execute()方法用来接收一个Runnable接口的对象,而ExecutorService接口中的submit()方法可以接受Runnable和Callable接口的对象;
    • Executor 中的execute()方法不返回任何结果,ExecutorService的submit()方法可以通过一个Future对象返回运算结果;
    • ExecutorService允许客户端提交一个任务,并且还提供用来控制线程池的方法,比如:调用shutDown()方法终止线程池

它们间的执行关系:

Executor框架、ThreadPoolExecutor、3种常见的线程池

分析说明:

  1. 主线程首先创建实现Runnable或Callable接口的任务对象;
  2. 工具类Executors可以把一个Runnable对象封装为一个Callable对象,使用如下两种方式:
    1. Executors.callable(Runnable task)
    2. Executors.callable(Runnable task,Object resule)。
  3. 然后可以把Runnable对象直接提交给ExecutorService执行,方法为ExecutorService.execute(Runnable command);
  4. 或者也可以把Runnable对象或者Callable对象提交给ExecutorService执行,方法为ExecutorService.submit(Runnable task)或ExecutorService.submit(Callable<T> task)。
    1. 这里需要注意的是如果执行ExecutorService.submit(...),ExecutorService将返回一个实现Future接口的对象(其实就是FutureTask)。
    2. 当然由于FutureTask实现了Runnable接口,我们也可以直接创建FutureTask,然后提交给ExecutorService执行。

到此Executor框架的主要体系结构我们都介绍完了,我们对此有了大概了解后,下面我们就重点聊聊两个主要的线程池实现类。

2.ThreadPoolExecutor

3. 3种常见的线程池

它们都直接或者间接地通过配置ThreadPoolExecutor来实现自己的功能特性,这个3种线程池分别是FixedThreadPool,CachedThreadPool,ScheduledThreadPool以及SingleThreadExecutor。