在一个应用当中, 我们往往需要多次使用线程, 这意味着我们需要多次创建和销毁线程.那么为什么不提供一个机制或概念来管理这些线程呢? 该创建的时候创建, 能复用的时候复用, 何乐而不为呢? Java 中开辟了一种管理线程的概念 , 即为线程池.
那么究竟线程池有啥好处? 为啥要用它呢?
具象来说, 线程池可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
由此可见 , 线程池在时间, 空间和效率上对程序运行都有好处, 那么为何不一起来学学呢?
承接线程池的好处, 线程的作用也可以总结为 控制系统中线程的数量, 合理调配线程执行任务.
具体来说:
Executor
线程池的顶级接口
ExecutorService
真正的线程池接口
ScheduledExecutorService
能和Timer/TimerTask类似,解决那些需要任务重复执行的问题
AbstractExecutorService 抽象类
ThreadPoolExecutor
是ExecutorService的默认实现,也是我们重点讨论的部分
ScheduledThreadPoolExecutor
继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现
Executors
线程池中的工具类 , 类比于集合类中的Connections之类的
下面我们来看看ThreadPoolExecutor是如何创建线程池的
ThreadPoolExecutor是线程池的真正实现,他通过构造方法的一系列参数,来构成不同配置的线程池。常用的构造方法有四个, 但其中最重要(全面)的一个如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
这个构造方法确定了创建的线程池的各种属性, 我们通过介绍其中各参数来介绍与之相关概念. 在下一篇文章中, 再详细介绍各种参数对应的各种线程池及其使用.
下面一张图来体会线程池和这几个参数:
由上图: 我们看到 corePoolSize 就是线程池中的核心线程数量, 这几个核心线程, 在没有用的时候也不会被回收; maximumPoolSize就是线程池中可以容纳的最大线程数量; 而keepAliveTime , 就是线程池中除核心线程的非核心线程的最长可存活时间, 因为在线程池汇总, 除了核心线程即使在无任务的时候也不能清除 , 其余的都是有存活时间的, 意思就是非核心线程可以存活的最长时间; 而util就是计算这个时间的一个单位; 图上方的 等待执行的任务队列 workQueue , 即等待队列, 任务可以存储在任务队列中等待被执行, 执行的原则是 FIFO ; threadFactory,就是创建线程的线程工厂; 最后一个handler,是一种拒绝策略,我们可以在任务满了之后 , 拒绝执行某些任务 .
下面分别介绍构造方法参数:
corePoolSize
核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。 (如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程)
maximumPoolSize
线程池所能容纳的最大线程数 ( maximumPoolSize肯定是大于等于corePoolSize)。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
keepAliveTime
非核心线程的闲置超时时间,超过这个时间就会被回收。
unit
指定keepAliveTime的单位,如 TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。
workQueue
线程池中的任务队列. 常用的有三种队列,SynchronousQueue, LinkedBlockingDeque, ArrayBlockingQueue (下文会详细介绍这几个任务队列)
threadFactory
线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法
public interface ThreadFactory {
Thread new Thread(Runnable r);
}
RejectedExecutionHandler
当线程边界和队列容量已经达到最大时,用于处理阻塞时的程序 , RejectedExecutionHandler也是一个接口,只有一个方法
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
}
看到这, 又是核心又是非核心的 , 还有存活时间 , 任务队列,拒绝啥的, 如果初次接触线程池可能都晕了, 下面我们来看看线程池是如果执行的 , 这些个概念也就能连接起来.
通过图, 我们再用文字梳理 下:任务进来时, 首先执行判断, 判断核心线程是否处于满员/ 不可用 状态,
在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务, 根据workQueue的不同性质, 分为以下三种
workQueue的类型为BlockingQueue,通常可以取下面三种类型:
1)有界任务队列ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)无界任务队列LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)直接提交队列synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务.
拒绝策略是在核心线程数量达到最大限度后 , 队列也满了, 非核心也最大数量了 , 拒绝当前任务的策略, 主要分为以下四种:
AbortPolicy:丢弃任务并抛出RejectedExecutionException
CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
DiscardOldestPolicy:丢弃队列中最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
DiscardPolicy:丢弃任务,不做任何处理。
根据上述的任务队列类型, 以及相应的参数的搭配 , Java抽离出线程池常用的五种类型五种类型
CachedThreadPool
可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
特征:
(1)线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
(2)线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
(3)当线程池中,没有可用线程,会重新创建一个线程
FixedThreadPool
定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
特征:
(1)线程池中的线程处于一定的量,可以很好的控制线程的并发量
(2)线程可以重复被使用,在显示关闭之前,都将一直存在
(3)超出一定量的线程被提交时候需在队列中等待
SingleThreadExecutor
一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程, 他只有一条线程来执行任务,适用于有顺序的任务的应用场景
特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
ScheduleThreadPool
创建一个线程池, 它可安排在给定延迟后运行命令或定期地执行.
周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
特征:
(1)线程池中具有指定数量的线程,即便是空线程也将保留
(2)可定时或者延迟执行线程活动
newSingleThreadScheduledExecutor
作用: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
(2)可定时或者延迟执行线程活动
最核心的execute方法,这个方法在AbstractExecutorService中并没有实现,从Executor接口,直到ThreadPoolExecutor才实现了改方法,
ExecutorService中的submit(),invokeAll(),invokeAny()都是调用的execute方法,所以execute是核心中的核心,源码分析将围绕它逐步展开。
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
本文是线程池系列的第一篇, 主要介绍一些java线程池中的概念, 直观感受线程池的创建和工作流程.
在接下来的文章中, 我们将讨论线程如何包装自己得使得线程池来执行他, 换句话说, 线程池究竟是怎样执行线程们的? 是不是new 一个 runnable丢到线程池中 (即详细讨论Executor, execute方法, runnable , callable , 以及其返回类型future之类的)
之后将详细介绍这ExecutorService以及其ThreadPoolExecutor,包括这几种几种线程池的使用.
[Java高并发系列(1)]Java 中 synchronized 关键字详解 + 死锁实例
[Java高并发系列(2)]Java 中 volatile 关键字详解 + volatile 与 sychronized 区别
[Java高并发系列(3)]Java 中 CountDownLatch介绍 + 一道面试题
[Java高并发系列(4)]Java 中 ReentrantLock 介绍 + 一道面试题