公司发的小师妹问我java中的线程池,这么讲可还行?
Posted 三太子敖雪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了公司发的小师妹问我java中的线程池,这么讲可还行?相关的知识,希望对你有一定的参考价值。
前言
最近在准备面试资料,就看了一下多线程相关的文章,但是找了很多文章发现大家写的差不多一样,有的文章同样的内容,但是给出的答案却是不一样的,所有我就自己参考一些自己买的书籍和看一下源码总结了一下,也就是以下内容。
这里都是线程池相关的内容,也就是ThreadPoolExecutor这个类相关的内容,至于Executors框架,我会单独再写一篇文章进行详细的写一下,也作为自己的面试总结。
一、线程池的实现原理
1、当提交一个任务到线程池时,线程池的处理流程
1、判断核心线程池里的核心线程是否已满
- 是,判断工作队列是否已经满了
- 否,不管核心线程有没有空闲,都创建一个新的线程执行这个任务
2、工作队列是否已经满了
- 已满,判断线程池是否满了
- 未满,将任务存储在工作队列里
3、判断线程池是否满了【核心线程以外的可以创建的线程,最大线程数】
- 已满,交给饱和策略
handle
处理无法执行的任务- 未满,创建一个新的线程执行这个任务
以上流程的图解
1、 线程池刚创建时是没有线程的,并且里面包含了一个任务队列。
2、 小于核心线程数(
corePoolSize
),不管有没有空闲的核心线程任务来了都创建一个新的线程;3、 大于等于核心线程数(
corePoolSize
)且小于设置的最大线程数(maximumPoolSize
),先加入到任务队列(workQueue
)里;4、 队列满了再继续创建线程,直到线程数到达设置的最大线程数(
maximumPoolSize
);5、 如果达到最大线程数(
maximumPoolSize
)且任务队列(workQueue
)满了,则通过 handler 所指定的饱和策略来处理此任务;6、 当线程池中的线程数量大于核心线程数(
maximumPoolSize
)时,如果某个线程空闲时间超过keepAliveTime
,线程将被终止,这样线程池可以动态的调整池中的线程数。
2、ThreadPoolExecutor
执行executor()
方法的四种
情况
1、 如果当前运行的线程少于
corePoolSize
,则创建新的线程来执行任务【全局锁】2、 如果运行的线程数大于等于
corePoolSize
,则将任务加入BlockingQueue
阻塞队列3、 如果
BlockingQueue
阻塞队列已满,无法加入新的任务,则创建新的线程来处理【全局锁】4、 如果创建的新的线程促使正在运行的线程数超过了
maximumPoolSize
,任务将被拒绝,并调用RejectedExecutionHandler
的rejectedExecution()
方法
3、线程池里的线程执行任务分两种情况
1、在
executor()
方法中创建一个线程,会让这个线程执行当前任务2、线程在执行完当前任务以后,队列里有任务就会不停的从
BlockingQueue
中取任务执行。
二、线程池的使用
1、线程池的创建
1.1、new 一个ThreadPoolExecutor调用的构造方法的源码
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Executor框架中创建的额线程池,都是基于该构造方法创建的。
1.2、ThreadPoolExecutor构造方法的参数解析
1、int corePoolSize
线程池的基本大小
当提交一个线程的时候,线程池会创建一个新的基本线程来执行任务,即使其他空闲的基本线程能够执行新的任务也会创建基本线程
除非需要执行的任务数大于线程池的基本大小,才不会创建新的基本线程
调用线程池的
prestartAllCoreThreads()
方法线程池会提前创建并启动所有的基本线程
2、int maximumPoolSize
线程池允许创建的最大线程数
如果队列满了,并且已经创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。
无界队列时,这个参数没有意义
3、long keepAliveTime
线程活动保持的时间
线程池的工作线程空闲后,保持存活的时间。
如果任务多,每个任务的执行时间短,可以调大时间,提高线程的利用率
4、TimeUnit unit
- 线程活动保持的时间的单位
5、BlockingQueue workQueue
- 用于保持等待执行任务的阻塞队列
6、ThreadFactory threadFactory
- 用于设置创建线程的工厂
- 可以通过线程工厂给每个创建出来的线程设置个名字
7、RejectedExecutionHandler handler
- 饱和策略,就是队列和线程都满了以后的策略
- 默认的策略是
AbortPolicy
,表示无法处理新的任务时候抛出【RejectedExecutionException
】异常。
1.3、常用的阻塞队列
ArrayBlockingQueue
- 有界阻塞队列
- 基于数组结构
- 按照先进先出【
FIFO
】原则对元素排序
LinkedBlockingQueue
- 无界阻塞队列
- 基于链表结构
- 按照先进先出【
FIFO
】原则对元素排序- 吞吐量高于
ArrayBlockingQueue
Executors.newFixedThreadPool()
使用的这个队列
SynchronousQueue
- 不存储元素的阻塞队列
- 每一个插入操作必须等到另一个线程调用移除操作
- 没有容量,不存储元素的阻塞队列,也即单个元素的队列,每一个put操作必须要等待一个take操作,否则不能继续添加元素
- 否则插入操作一直处于阻塞状态
- 吞吐量高于
LinkedBlockingQueue
Executors.newCachedThreadPool()
使用的这个队列
PriorityBlockingQueue
- 无界阻塞队列
- 具有优先级
- 内部使用数组存储数据,达到容量时,会自动进行扩容,放入的元素会按照优先级进行排序
1.4、四种饱和策略
1、AbortPolicy
- 该策略是线程池的默认策略。使用该策略时,如果线程池队列满了丢掉这个任务并且抛出
RejectedExecutionException
异常。
2、CallerRunsPolicy
只有调用者所在的线程来运行该任务。
谁调用,谁执行。
3、DiscardOldestPolicy
将最早进入队列的任务删除,之后再尝试加入队列。
因为是队列吗,先从任务队列弹出最先加入的任务,空出一个位置,然后再次执行
execute
方法把任务加入队列。
4、DiscardPolicy
- 不做任何处理,直接丢掉这个任务,不会有异常抛出。
2、向线程池提交任务
2.1、execute()方法
1、
execute()
方法用于提交不需要返回值的任务2、无法判断任务是否被线程池执行成功
2.2、submit()方法
1、
submit()
方法用于提交需要返回值的任务,会返回一个future类型的对象2、通过
future
类型的对象可以判断任务是否执行成功3、也可以通过
future
类型的对象的get()
方法获取返回值4、
future
类型的对象的get()
方法会阻塞当前线程知道任务完成
3、自定义一个线程池
private static final int THREAD_POOL_SIZE = 5;
public static ThreadPoolExecutor threadPool(){
// 使用 ThreadFactoryBuilder 创建自定义线程名称的 ThreadFactory,需要引入guava 的maven依赖
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("mine-task-pool-%d").build();
// 创建线程池,其中任务队列需要结合实际情况设置合理的容量
return new ThreadPoolExecutor(THREAD_POOL_SIZE,
THREAD_POOL_SIZE,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(8),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy());
}
三、关闭线程池
1、 使用shutdown
关闭线程池
将线程池状态置为
SHUTDOWN
状态,并不会立即停止内部正在跑的任务和队列里等待的任务,会执行完
中断所有没有正在执行任务的线程,也不会接收新的任务
2、 使用shutdownNow
关闭线程池
将线程池状态置为
STOP
先停止接收外部提交的任务
尝试停止所有正在执行任务的线程和暂停任务的线程
尝试停止并不是立即停止,尝试将正在跑的任务interrupt中断
返回等待执行任务的列表
3、 两种关闭的原理
- 遍历线程池中的工作线程,然后逐个调用线程的
interrupt
方法来中断线程。- 无法中断响应中断的任务可能永远无法终止。
四、合理的配置线程池
1、 CPU密集型任务
CPU
密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%
,CPU
要读/写I/O
(硬盘/内存),I/O
在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading
很高。- 应配置尽可能少的线程,如配置
CPU
个数加1个线程的线程池。
2、 IO密集型任务
IO
密集型指的是系统的CPU
性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU
在等I/O
(硬盘/内存) 的读/写操作,此时CPU Loading
并不高。线程并不是一直在执行任务,要配置尽可能多的线程,如2倍
CPU
个数线程的线程池
3、 混合型任务
- 拆分为
CPU
密集型任务和IO
密集型任务
4、 一般计算设置的线程数的公式
- 线程数=
cpu核数
*(cpu计算时间+io等待时间
)/cpu计算时间
五、合理使用线程池带来的三个好处
1、降低资源的消耗
- 重复利用已经创建的线程,降低线程的创建和销毁带来的消耗
2、提高响应速度
- 当任务到达的时候,任务不需要等待线程的创建可以直接使用线程池里已经创建的线程
3、提高线程的可管理性
- 线程池可以进行统一的分配、调优和监控
六、知识脑图
谢谢各位的一键三连
- 创作不易, 非常欢迎大家的点赞、评论和关注(^_−)☆
- 你的点赞、评论以及关注是对我最大的支持和鼓励
- 是我继续创作高质量博客的动力 !!!
以上是关于公司发的小师妹问我java中的线程池,这么讲可还行?的主要内容,如果未能解决你的问题,请参考以下文章
公司发的小师妹夸我好棒,因为我告诉了她项目中用的双重检查锁定是怎么回事
公司发的小师妹夸我好棒,因为我告诉了她项目中用的双重检查锁定是怎么回事