java线程池相关知识整理

 

什么是线程池(thread pool)?

在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下图(图1)所示:

java线程池相关知识整理_第1张图片

  • newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  • newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
  • newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
  • newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

补充:java.utils.concurrent的三个Executor接口:

  • Executor:运行新任务的简单接口,将任务提交和任务执行细节解耦
// 普通创建并启动线程的伪代码:
Thread t = new Thread();
t.start();

// 使用executor创建并启动线程的伪代码:
Thread t = new Thread();
executor.execute(t);
  • ExecutorService:具备管理执行器和任务生命周期的方法,提交任务机制更完善
  • ScheduledExecutorService:支持Future和定期执行任务

 

为什么要用线程池?

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能执行。
  • 提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

 

线程池的种类?

即图1内容,Java通过Executors提供四种线程池,分别为:

  • newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。 
  • newCachedThreadPool创建一个可缓存的线程池。这种类型的线程池特点是: 
    1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。 
    2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
  • newSingleThreadExecutor创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的 。 
  • newScheduleThreadPool创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

 

ThreadPoolExecutor

在大多数应用场景下,使用Executors提供的几种线程池基本能满足需求了,但还是有些情况需要我们使用ThreadPoolExecutor去自己创建一个线程池。如下图是当线程池被使用时的基本工作流程。

java线程池相关知识整理_第2张图片

ThreadPoolExecutor构造参数

  • corePoolSize:核心线程的数量
  • maximumPoolSize:核心线程不够用时,线程池能够创建的最大线程数
  • workQueue:任务等待队列
  • keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程大于corePoolSize时,如果没有新的任务提交,corePoolSize外的线程不会被立即销毁,而是会等待keepAliveTime的时间,然后才被销毁。
  • threadFactory:主要用来创建新线程(用Executors.defaultThreadFactory()创建)。

注:还有一个参数:handler(线程池的饱和策略),主要提供四种处理策略:

  1. AbortPolicy:默认策略,直接抛出异常
  2. CallerRunsPolicy:使用调用者所在的线程来执行任务
  3. DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行该任务
  4. DiscardPolicy:直接丢弃该任务
  5. 自定义策略:实现RejectExecutionHandler接口自定义的handler

 

新任务提交execute后的执行流程

  • 如果当前运行的线程少于corePoolSize,那么即使线程池中其他线程是空闲的,也会创建新线程去执行任务。
  • 如果线程池中的线程大于等于corePoolSize且小于maximumPoolSize,那么只有当workQueue满了才会创建新线程执行。
  • 如果设置的maximumPoolSize和corePoolSize相等,线程池的大小就是固定的。这时如果有新任务提交,若workQueue未满则会进入workQueue等待有空闲的线程去workQueue取任务。
  • 如果要运行的线程数量大于等于maximumPoolSize,且此时workQueue也已经满了,则通过handler指定的策略处理新加的任务。

 

线程池的状态

  • RUNNING:能接收新提交的任务,也能处理阻塞的workQueue中的任务
  • SHUTDOWN:不再接收新提交的任务,但可以处理存量任务
  • STOP:不再接收新提交的任务,也不处理存量任务
  • TIDYING:所有任务都已终止不再处理
  • TERMINATED:已终止,terminated()方法执行后进入的状态

附上状态转换图,表示各种状态间如何切换:

java线程池相关知识整理_第3张图片

 

补充:线程池的大小如何选定?

根据经验,线程池的大小可以按照下述原则选定:

你可能感兴趣的:(java线程池相关知识整理)