线程池
线程池介绍
软件中的"池"可以理解为计划经济
如果不使用线程池,每个任务都新开一个线程处理:例如使用for循环创建线程,当任务数量上升到1000时开销就会很大了(如报内存不足异常),我们希望有固定数量的线程来执行这1000个线程,避免反复创建并销毁线程带来的开销问题
使用线程池解决:反复创建线程开销大;过多的线程会占用太多内存
解决思路:使用少量线程避免内存占用过多;让这部分线程都保持工作,且可以反复执行任务,避免生命周期的损耗
线程池的好处:加快相应速度;合理利用CPU和内存;统一管理资源;
适用场景:服务器接收到大量请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率
实际上,在开发中,如果需要创建5个以上的线程,就可以使用线程池来管理
创建和停止线程池
线程池构造方法的参数
corePoolSize指的是核心线程数 - 线程池再完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务
最大量maxPoolSize - 在核心线程的基础上,额外增加的线程数的上限
添加线程规则
如果线程数小于corePoolSize,创建一个新线程来运行任务
如果等于(或大于)corePoolSize但少于maximumPoolSize,则将任务放入队列
如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程
如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝
增减线程的特点
- 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池
- 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它
- 通过设置maximumPoolSize为很高的值,可以运行线程池容纳任意数量的并发任务
- 只有在队列填满时才创建多于corePoolSize的线程,如果使用的是无界队列,那么线程数就不会超过corePoolSize
KeepAliveTime
如果线程池当前线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止
ThreadFactory 用来创建线程
默认使用Executors.defaultThreadFactory()
创建出来的线程都在同一个线程组
如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否守护线程等
工作队列
常见的队列类型:
-
直接交接:SynchronousQueue
-
无界队列:LinkedblockingQueue
-
有界的队列:ArrayblockingQueue
-
DelayedWorkQueue 延迟队列
线程池应该手动创建还是自动创建
手动创建更好,因为这样可以更加明确线程池的运行规则,避免资源耗尽的风险
自动创建线程池(即直接调用JDK封装好的构造方法)可能带来哪些问题?
newFixedThreadPool()
容易造成大量内存占用,可能会导致OOM内存错误
newSingleThreadExecutor()
原理和1类似,只是线程最大数量为1,当请求堆积时仍可能会占用大量的内存
newCachedThreadPool()
可缓存线程池 特点:具有自动回收多余线程的功能
弊端在于第二个参数maximumPoolSize被设置为了Integer.MAX_VALUE,这可能会创建非常多的线程,甚至导致OOM
newScheduledThreadPool()
支持定时及周期性任务执行的线程池
以上4种线程池的构造方法的参数
阻塞队列分析
FixedThreadPool:LinkedblockingQueue无界
CachedThreadPool:SynchronousQueue直接交接
ScheduledThreadPool:DelayedWorkQueue延迟队列
workStealingPool是JDK1.8加入的
和之前的线程池有很大不同
子任务:当任务可以产生子任务适合这种场景,比如处理树、矩阵等
窃取:当一个线程有子任务,其他线程空闲可以帮忙执行其它线程的子任务,特点是为了提高效率任务不能有枷锁,不保证执行顺序;使用递归任务适合
正确的创建线程池的方法
根据不同的业务场景,设置线程池参数
比如:内存有多大,给线程取什么名字等等
线程池数量
线程池里的数量设定为多少比较合适
CPU密集型(加密、计算hash等):最佳线程数为CPU核心数的1-2倍左右
耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于CPU核心数的很多倍
参考Brain Goetz推荐的计算方法:
线程数=CPU核心数*(1+平均等待时间/平均工作时间)
停止线程池的方法
-
shutdown 关闭线程池,但运行这个方法时线程池不一定被停止,只是执行关闭任务,要先等待线程池中的任务完成,执行shutdown后再有新任务提交将被拒绝
-
isShutdown 返回布尔值判断线程是否停止(返回线程停止状态,但仍有可能有在运行的线程)
-
isTerminated 返回线程是否完全停止!
-
awaitTermination 测试传入的一段时间内线程是否完全停止
-
shutdownNow 立刻停止线程,通知正在执行的线程中断,让在队列中等待的线程列表返回
List<Runnable>
任务太多,怎么拒绝?
拒绝时机
- 当Executor关闭时,提交新任务会被拒绝
- 当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时
4种拒绝策略
AbortPolicy 抛出异常
DiscardPolicy 丢弃,不会通知任务
DiscardOldestPolicy 丢弃最老的任务,存放时间最久的
CallerRunsPolicy 让提交任务的线程去执行任务
钩子方法,给线程池加点料
每个任务执行前后;暂停、恢复方法
可以进行日志、统计
实现原理、源码分析
线程池组成部分:
- 线程池管理器
- 工作线程
- 任务队列
- 任务接口(Task)
Executor家族
线程池状态
使用线程池的注意点
避免任务堆积
避免线程数过度增加
排查线程泄露