简述线程池
1. 为什么要用线程池
降低资源消耗
提高响应速度
提高线程的可管理性
2. JUC里面线程池代码体系
ThreadPoolExecutor类实现了ExcecutorService接口和Executor接口,并有Executors类扮演线程池工厂的角色
线程池核心参数:
corePoolSize: 线程池维护线程最少的数量
maximumPoolSize: 线程池维护线程的最大数量
keepAliveTime: 线程池维护线程所允许的最大空闲时间
unit: 线程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓存队列
handler: 线程池对拒绝任务的处理策略
3. 常规实现的线程池(通过创建不同的ThreadPoolExecutor对象)
(1) newFixedThreadPool 定长线程池
corePoolsize 和 maximunPoolSize 都为用户设定的线程数量nThreads;
keepAliveTime为0, 意味着一旦有多余的空间,就会被立即停止掉。
阻塞队列采用LinkedBLockingQueue 是一个无界的队列。
由于采用了阻塞队列,实际线程数量永远维持在nThreads,后面两个参数均无效。
(2) newCashedThreadPool 可缓存
它是一个无限扩大的线程池
它比较适合处理执行任务实际比较小的任务
corePoolSize为0,maximumPoolSize为无限大。
keepAliveTime为60s,意味着线程空闲时间超过60S就会被杀死。
采用SynchronousQueue装等任务,这个阻塞队列没有存储空间,意味着只要任务来,就必须找到一条工作线程处理他
(3) newSingleThreadExecutor 单一线程池
它只会创建一条工作线程处理任务
采用阻塞队列为LinkedBlockQueue
(4) ScheduledThreadPool 可调度线程池
实现周期性线程调度,比较常用
4. 讲述BlockingQueue
直接提交队列,有界队列,无界队列,优先级队列
(1) 直接提交队列
SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会被阻塞,需要再执行一个删除操作才会被唤醒。
(2) 有界的任务队列
有界的任务队列可以使用ArrayBlockQueue实现,若有新任务需要执行时,线程池才会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中,若等待队列已满,则会创建新线程,直到线程数量达到maximumPoolSize,设置的最大线程数量,则执行拒绝策略。
(3) 无界任务队列
此时最大线程数量会失效。
(4) 优先任务队列
可以通过自定义规则来执行任务。
15. 线程池的增长策略
当一个任务通过execute(Runnable)方法欲添加到线程池时:
1. 如果工作线程的数量小于corePoolSize,直接添加
2.如果工作线程等于线程核心数,阻塞队列未满,那么添加阻塞队列
3.如果核心线程慢,阻塞队列满,那么开启新的线程池,直到最大线程数
4.如果都满,启动拒绝策略。
16. 线程池的拒绝策略
1. AbortPolicy: 这种策略直接抛出异常,丢弃任务
2. CallerRunsPolicy: 直接使用当前线程执行新的任务
3. DiscardPolicy: 不能执行任务删除,不抛异常
4. DiscardOldestPolicy: 如果执行尚未关闭,则位于队列头部的任务被删除,然后重新执行。
1. 为什么要用线程池?
线程池提供了一种限制和管理资源(包括执行一个任务)的方式。 每个线程池还维护
一些基本统计信息,例如已完成任务的数量。
使用线程池的好处:
1、降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
2、提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行;
3、提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资
源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2. 实现 Runnable 接口和 Callable 接口的区别?
如果想让线程池执行任务的话需要实现的 Runnable 接口或 Callable 接口。
Runnable 接口或 Callable 接口实现类都可以被 ThreadPoolExecutor 或
ScheduledThreadPoolExecutor 执行。两者的区别在于 Runnable 接口不会返回结果但
是 Callable 接口可以返回结果。
备注: 工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转
换。(Executors.callable(Runnable task)或 Executors.callable(Runnable
task,Object resule))。
4. 如何创建线程池?
《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过
ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,
规避资源耗尽的风险
Executors 返回线程池对象的弊端如下:
FixedThreadPool 和 SingleThreadE xecutor : 允许请求的队列长度为
Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为
Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
方式二:通过 Executor 框架的工具类 Executors 来实现
我们可以创建三种类型的 ThreadPoolExecutor:
1. FixedThreadPool : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量
始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则
新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
2. SingleThreadExecutor:方法返回一个只有一个线程的线程池。若多余一个任务被提
交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先进先出的顺序执行队
列中的任务。
3. CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。线程
池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线
程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行
完毕后,将返回线程池进行复用。
5. 线程池的参数
1、corePoolSize(线程池的基本大小):当提交一个任务到线程池时,如果当前
poolSize < corePoolSize 时,线程池会创建一个线程来执行任务,即使其他空闲的基本
线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再
创建。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有
基本线程。
2、maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列
满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得
注意的是,如果使用了无界的任务队列这个参数就没什么效果。
3、keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时
间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的
利用率。
4、TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时
(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒
(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。
5、workQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个
阻塞队列:
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先
进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO 排序元
素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法
Executors.newFixedThreadPool() 使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个
线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于
LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了
这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
6、threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程
设置更有意义的名字。使用开源框架guava提供的ThreadFactoryBuilder可以快速给线程
池里的线程设置有意义的名字,代码如下。
7、RejectExecutionHandler(饱和策略):队列和线程池都满了,说明线程池处于饱和
状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是 AbortPolicy,
表示无法处理新任务时抛出异常。在 JDK 1.5 中 Java 线程池框架提供了以下 4 种策略:
AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。
当然,也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策
略。如记录日志或持久化存储不能处理的任务。