java线程池核心类ThreadPoolExecutor概述

createh51个月前 (04-05)技术教程8

前言

前文java中的阻塞队列和非阻塞队列我们介绍了常用的几种队列,队列的使用很广泛,特别是一些需要生产消费模式的场景以及需要对全局的集合进行操作的场景。今天我们来讲讲其中的一种应用——线程池。

我们从java 多线程实现方式知道,有三种常见的创建线程的方法:继承Thread类、实现Runnable接口和实现Callable接口。这些线程在运行结束后都会被虚拟机销毁,如果线程数量多的话,频繁的创建和销毁线程会大大浪费时间和效率。更重要的是浪费内存,当线程执行完毕后死亡,线程对象就变成垃圾,造成GC的频繁收集和停顿。

我们使用线程池来解决这个问题,让线程运行完不立即销毁,并且重复使用,继续执行其他的任务。使用线程池来管理线程,一方面使线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。

核心类

在java.util.concurrent包中我们能找到线程池的定义,其中ThreadPoolExecutor是我们线程池的核心类,我们先看下构造函数:

public ThreadPoolExecutor(int corePoolSize,
 int maximumPoolSize,
 long keepAliveTime,
 TimeUnit unit,
 BlockingQueue workQueue,
 ThreadFactory threadFactory,
 RejectedExecutionHandler handler) {
 if (corePoolSize < 0 ||
 maximumPoolSize <= 0 ||
 maximumPoolSize < corePoolSize ||
 keepAliveTime < 0)
 throw new IllegalArgumentException();
 if (workQueue == null || threadFactory == null || handler == null)
 throw new NullPointerException();
 this.acc = System.getSecurityManager() == null ?
 null :
 AccessController.getContext();
 this.corePoolSize = corePoolSize;
 this.maximumPoolSize = maximumPoolSize;
 this.workQueue = workQueue;
 this.keepAliveTime = unit.toNanos(keepAliveTime);
 this.threadFactory = threadFactory;
 this.handler = handler;
}

构造函数的参数含义:

  • corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
  • maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
  • keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
  • unit:keepAliveTime的单位;
  • workQueue:存放提交的任务,实现队列的方式有:BlockingQueueLinkedBlockingQueueSynchronousQueueSynchronousQueue等,关于队列的选择要根据实际情况来确定;
  • threadFactory:线程工厂,用于创建线程,一般用默认即可;
  • handler:拒绝策略;创建线程时,为了防止资源被耗尽,任务队列都会选择创建有界任务队列,但这种模式下如果出现队列已满且线程池创建的线程数达到最大的线程数时,就需要用拒绝策略来处理线程池“超载”的情况。jdk默认的处理方式是AbortPolicy,抛出异常阻止程序(除非是安全性要求极高,否则在大并发情况下使用这种做法不是很明智);DiscardPolicy,丢弃无法处理的任务;DiscardOledesPolicy,也是丢弃任务,只不过丢弃的是队列最先被添加进去,马上要执行的任务;CallerRunsPolicy,由调用者所在线程来运行任务除了使用jdk提供的这四种策略之外,我们还可以通过实现RejectExecutionHandler来自定义拒绝策略。

一般流程图:

  1. 当线程池中线程数小于corePoolSize时,新提交的任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程;
  2. 当线程池中线程数达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 ;
  3. 当workQueue已满,且maximumPoolSize > corePoolSize时,新提交任务会创建新线程执行任务;
  4. 当workQueue已满,且提交任务数超过maximumPoolSize,任务由RejectedExecutionHandler处理;
  5. 当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收这些线程;
  6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收。

提交线程池

ExecutorService pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
pool.submit(xxRunnble); //方式1
pool.execute(xxRunnble); //方式2

两种方式的区别:execute没有返回值,如果不需要知道线程的结果就使用execute方法,性能比较好;submit返回一个Future对象如果想知道线程结果就使用submit提交,而且它能在主线程中通过Future的get方法捕获线程中的异常。

关闭线程池

pool.shutdown();//方式1
pool.shutdownNow();//方式2

两种方式的区别:shutdown不再接受新的任务,之前提交的任务等执行结束再关闭线程池;shutdownNow不再接受新的任务,试图停止池中的任务再关闭线程池,返回所有未处理的线程List列表。

总结

线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到多个任务上,而且由于在请求到达时线程已经存在,所以消除线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足。

相关文章

java 核心技术-12版 卷Ⅰ- 4.7.1 记录 record

原文4.7 记录有时,记录就只是数据,而面向对象程序设计提供的数据隐藏有些碍事。考虑一个类Point ,这个类描述平面上的一个点,有x和y 坐标。当然,可以如下创建一个类public class Po...

阿里大数据技术架构师整理分享java面试核心知识点框架篇文档

前言本文是对Java程序员面试中常见的微服务、网络编程、分布式存储和分布式计算等必备知识点的总结,包括Spring 原理及应用、Spring Cloud原理及应用、Netty网络编程原理及应用、Zoo...

java 核心技术-12版 卷Ⅰ- 4.8.7 设置类路径

原文4.8.7 设置类路径最好使用 -classpath(或 -cp, 或者Java 9 中的 --calss-path) 选项指定类路径:java -classpath /home/user/cla...

java 核心技术-12版 卷Ⅰ- 5.4 对象包装器与自动装箱

原文5.4 对象包装器与自动装箱有时,需要将 int 这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类,例如,Integer类对应基本类型 int。通常,这些类称为包装器 (wrapper...

Java核心知识 Zookeeper(二)角色

Zookeeper 集群是一个基于主从复制的高可用集群,每个服务器承担如下三种角色中的一种 Leader1. 一个 Zookeeper 集群同一时间只会有一个实际工作的 Leader,它会发起并维护与...