在Java中,使用线程来异步执行任务。Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源。同时,为每一个任务创建一个新线程来执行,这种策略可能会使处于高负荷状态的应用最终崩溃。

Java线程既是工作单元,也是执行单元。从JDK1.5开始,把工作单元与执行机制分离开来。工作单元包括Runnable 和 Callable,而执行机制由Executor框架提供。

Executor框架简介

Executor框架的两级调度模型

在HotSpot VM的线程模型中,Java线程被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当Java线程终止时,这个操作系统线程也会被回收。操作系统会调用所有线程并将他们分配给可用的CPU。

可以将此种模式分为两层,在上层,Java多线程程序通常把应用程序分解为若干任务,然后使用用户级的调度器(Executor框架)讲这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。

两级调度模型的示意图:

从图中可以看出,该框架用来控制应用程序的上层调度(下层调度由操作系统内核控制,不受应用程序的控制)。

Executor框架的结构和成员

Executor框架的结构

1. 任务

包括被执行任务需要实现的接口:Runnable接口和Callable接口

2. 任务的执行

包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。

Executor框架有两个关键类实现了ExecutorService接口:ThreadPoolExecutor和 ScheduledThreadPoolExecutor

3. 异步计算的结果

包括Future和实现Future接口的FutureTask类。

Executor框架的类与接口

示意图

  • Executor是一个接口,他是Executor框架的基础,它将任务的提交与任务的执行分离。
  • ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
  • ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor 比 Timer 更灵活,功能更强大。
  • Future接口和它的实现FutureTask类,代表异步计算的结果。
  • Runnable和Callable接口的实现类,都可以被ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。

Executor框架的使用

先来看个图:

  1. 主线程首先要创建实现 Runnable接口或者Callable接口的任务对象。工具类Executors可以把一个Runnable对象封装为一个Callable对象

    1. Executors.callable(Runnale task);

    2. Executors.callable(Runnable task, Object resule);
  2. 然后可以把Runnable对象直接交给ExecutorService执行
    1. ExecutorServicel.execute(Runnable command);
    2. 或者也可以把Runnable对象或Callable对象提交给ExecutorService执行
    3. ExecutorService.submit(Runnable task);

    如果执行ExecutorService.submit(...),ExecutorService将返回一个实现Future接口的对象(到目前为止的JDK中,返回的是FutureTask对象)。由于FutureTask实现了Runnable接口,我们也可以创建FutureTask类,然后直接交给ExecutorService执行。  

  3. 最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

ThreadPoolExecutor详解

Executor框架最核心的类是ThreadPoolExecutor

 ThreadPoolExecutor的组件构成

  • corePool:核心线程池的大小
  • maximumPool:最大线程池的大小
  • BlockingQueue:用来暂时保存任务的工作队列
  • RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和时(达到了最大线程池的大小且工作队列已满),execute()方法将要调用的Handler。

Executor 可 以 创 建 3 种 类 型 的 ThreadPoolExecutor线 程 池:

1. FixedThreadPool

创建固定长度的线程池,每次提交任务创建一个线程,直到达到线程池的最大数量,线程池的大小不再变化。

这个线程池可以创建固定线程数的线程池。特点就是可以重用固定数量线程的线程池。它的构造源码如下:

  1. public static ExecutorService newFixedThreadPool(int nThreads) {
  2. return new ThreadPoolExecutor(nThreads, nThreads, 0L,
  3. TimeUnit.MILLISECONDS,
  4. new LinkedBlockingQueue<Runnable>());
  5. }
  • FixedThreadPool的corePoolSize和maxiumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。
  • 0L则表示当线程池中的线程数量操作核心线程的数量时,多余的线程将被立即停止
  • 最后一个参数表示FixedThreadPool使用了无界队列LinkedBlockingQueue作为线程池的做工队列,由于是无界的,当线程池的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池的线程数量不会超过corePoolSize,同时maxiumPoolSize也就变成了一个无效的参数,并且运行中的线程池并不会拒绝任务。

FixedThreadPool运行图如下

执行过程如下:

1.如果当前工作中的线程数量少于corePool的数量,就创建新的线程来执行任务。

2.当线程池的工作中的线程数量达到了corePool,则将任务加入LinkedBlockingQueue。

3.线程执行完1中的任务后会从队列中去任务。

注意LinkedBlockingQueue是无界队列,所以可以一直添加新任务到线程池。

2. SingleThreadExecutor  

SingleThreadExecutor是使用单个worker线程的Executor。特点是使用单个工作线程执行任务。它的构造源码如下:

  1. public static ExecutorService newSingleThreadExecutor() {
  2. return new FinalizableDelegatedExecutorService
  3. (new ThreadPoolExecutor(1, 1,
  4. 0L, TimeUnit.MILLISECONDS,
  5. new LinkedBlockingQueue<Runnable>()));
  6. }

  

SingleThreadExecutor的corePoolSize和maxiumPoolSize都被设置1。
其他参数均与FixedThreadPool相同,其运行图如下:

执行过程如下:

1.如果当前工作中的线程数量少于corePool的数量,就创建一个新的线程来执行任务。

2.当线程池的工作中的线程数量达到了corePool,则将任务加入LinkedBlockingQueue。

3.线程执行完1中的任务后会从队列中去任务。

注意:由于在线程池中只有一个工作线程,所以任务可以按照添加顺序执行。

3. CachedThreadPool

CachedThreadPool是一个”无限“容量的线程池,它会根据需要创建新线程。特点是可以根据需要来创建新的线程执行任务,没有特定的corePool。下面是它的构造方法:

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

  

CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为Integer.MAX_VALUE,即maximum是无界的。这里keepAliveTime设置为60秒,意味着空闲的线程最多可以等待任务60秒,否则将被回收。
 
CachedThreadPool使用没有容量的SynchronousQueue作为主线程池的工作队列,它是一个没有容量的阻塞队列。每个插入操作必须等待另一个线程的对应移除操作。这意味着,如果主线程提交任务的速度高于线程池中处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU资源。其运行图如下:

执行过程如下:

1.首先执行SynchronousQueue.offer(Runnable task)。如果在当前的线程池中有空闲的线程正在执行SynchronousQueue.poll(),那么主线程执行的offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行。,execute()方法执行成功,否则执行步骤2

2.当线程池为空(初始maximumPool为空)或没有空闲线程时,配对失败,将没有线程执行SynchronousQueue.poll操作。这种情况下,线程池会创建一个新的线程执行任务。

3.在创建完新的线程以后,将会执行poll操作。当步骤2的线程执行完成后,将等待60秒,如果此时主线程提交了一个新任务,那么这个空闲线程将执行新任务,否则被回收。因此长时间不提交任务的CachedThreadPool不会占用系统资源。

SynchronousQueue是一个不存储元素阻塞队列,每次要进行offer操作时必须等待poll操作,否则不能继续添加元素。

参考书籍:《Java并发编程的艺术》,《Java并发编程实战》,《Java高并发程序设计》

更多内容:http://www.cnblogs.com/study-everyday/

【Java多线程】Executor框架的详解的更多相关文章

  1. JAVA多线程Thread VS Runnable详解

    要求 必备知识 本文要求基本了解JAVA编程知识. 开发环境 windows 7/EditPlus 演示地址 源文件   进程与线程 进程是程序在处理机中的一次运行.一个进程既包括其所要执行的指令,也 ...

  2. Java 多线程同步和异步详解

    java线程 同步与异步 线程池 1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线 程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成 ...

  3. Java多线程之线程池详解

    前言 在认识线程池之前,我们需要使用线程就去创建一个线程,但是我们会发现有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因 ...

  4. java多线程环境单例模式实现详解

    Abstract 在开发中,如果某个实例的创建需要消耗很多系统资源,那么我们通常会使用惰性加载机制,也就是说只有当使用到这个实例的时候才会创建这个实例,这个好处在单例模式中得到了广泛应用.这个机制在s ...

  5. java多线程——同步块synchronized详解

    Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java同步块用来避免竞争.本文介绍以下内容: Java同步关键字(synchronzied) 实例方法同步 静 ...

  6. java多线程及线程安全详解

    为什么要使用多线程: 单线程只能干一件事  而多线程可以同时干好多事(将任务放到线程里执行  效率高) 而所谓同时干并不是真正意义上的同时   只是(这里就叫CPU)cpu在每个线程中随机切换来执行 ...

  7. Java多线程中join方法详解

    join()方法用于让当前执行线程等待join线程执行结束.其实现原理是不停的检查join线程是否存活,如果join线程存活则让当前线程永远等待. join()方法部分实现细节 while(isAli ...

  8. Java网络编程和NIO详解9:基于NIO的网络编程框架Netty

    Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...

  9. (转)Java并发包基石-AQS详解

    背景:之前在研究多线程的时候,模模糊糊知道AQS这个东西,但是对于其内部是如何实现,以及具体应用不是很理解,还自认为多线程已经学习的很到位了,贻笑大方. Java并发包基石-AQS详解Java并发包( ...

随机推荐

  1. 如何制作 Objective-C 的UML图 [1]

    如何制作 Objective-C 的UML图 [1] 说明 本教程旨在教你如何制作 Objective-C 的UML图,此为第一部分. 步骤 注册(在线制作) https://www.processo ...

  2. 编译并导入OpenSSL

    编译并导入OpenSSL 1. 首先,需要运行脚本生成OpenSSL库,参考 https://github.com/x2on/OpenSSL-for-iPhone 示例 2. 运行脚本生成静态库 下一 ...

  3. September 20th 2017 Week 38th Wednesday

    All our dreams can come true if we have the courage to pursue them. 如果我们有勇气去追求梦想,我们的梦想一定可以成为现实. If y ...

  4. php实现简单的单链表

    <?php /** * 建立一个链表,节点的data为数组,记录一个id,完成链表所以操作 */ //结点,结点数据data定义为一个数组,id和value class Node{ public ...

  5. SVN那些事

    非原创,引用于青松世界博客,最近在使用svn,因为方便复制内容在这查看.原地址 https://www.cnblogs.com/Rambo635755402/p/5549447.html Totois ...

  6. Python中网络编程对 listen 函数的理解

    listen函数的第一个参数时SOCKET类型的,该函数的作用是在这个SOCKET句柄上建立监听,至于有没有客户端连接进来,就需要accept函数去进行检查了,accept函数的第一个参数也是SOCK ...

  7. AOP:选择正确的时机进行编织

    在本文中,我们将采用三种重要的实现的例子,来实践本文提出的概念.这三种 AOP 实现是 AspectJ,Spring 和 JBoss.通过比较他们在 Weave 时机方面的不同,来获得对于如何选择 W ...

  8. OOP——构造函数、析构函数

    我们在创建和销毁对象时需要执行一些任务.例如,在创建对象时给属性赋值,在对象销毁时关闭数据连接等,这时就需要构造函数和析构函数. 在PHP中构造函数和析构函数是固定的,如下: // 构造函数 func ...

  9. 【洛谷】【堆】P1801 黑匣子_NOI导刊2010提高(06)

    [题目描述:] Black Box是一种原始的数据库.它可以储存一个整数数组,还有一个特别的变量i.最开始的时候Black Box是空的.而i等于0.这个Black Box要处理一串命令. 命令只有两 ...

  10. 对于高并发短连接造成Cannot assign requested address解决方法

    https://www.cnblogs.com/dadonggg/p/8778318.html 感谢这篇文章给予的启发 在tcp四次挥手断开连接时,主动释放连接的一方最后会进入TIME_WAIT状态, ...