线程池——治理线程的法宝
1. 线程池的自我介绍
-
线程池的重要性
-
什么是池
-
软件中的“池”,可以理解为计划经济
-
如果不使用线程池,每个任务都新开一个线程处理
- 一个线程
- for循环创建线程
- 当任务数量上升到1000
-
这样的开销太大,我们希望有固定数量的线程,来执行这1000个线程,这样就避免了反复创建并销毁线程所带来的开销问题
-
为什么要使用线程池
问题一:反复创建线程开销大
问题二:过多的线程会占用太多内存
- 解决以上两个问题的思路
- 用少量的线程——避免内存占用过多
- 让这部分线程都保持工作,且可以反复执行任务——避免生命周期的损耗
线程池的好处
- 加速响应速度
- 合理利用CPU和内存(灵活配置达到性能最佳状态)
- 统一管理
线程池适合应用的场景
- 服务器接受到大量请求时,使用线程池技术是非常合适的。它可以大大减少线程的创建和销毁次数,提搞服务器的工作效率。
2. 创建和停止线程
线程池构造函数的参数
参数名 | 类型 | 含义 |
---|---|---|
corePoolSize | int | 核心线程数 |
maxPoolSize | int | 最大线程数 |
KeepAliveTime | long | 保持存活时间 |
workQueue | BlockQueue | 任务存储队列 |
ThreadFactory | ThreadFactory | 当线程池需要新的线程的时候,会使用threadFactory来生成新的线程 |
Handler | RejectExecutionHandler | 由于线程池无法接收新提交的任务的拒绝策略 |
- corePoolSize:指的是核心线程数,线程池在初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,在创建新线程去执行任务。
- maxPoolSize:线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程数有一个上限,这就是最大量maxPoolSize。
- KeepAliveTime:如果线程池当前的线程数多于corePoolSize,那么多于的线程空闲时间超过KeepAliveTime,它们就会终止。
如上图所示,core Pool Size为初始化线程池容量大小,随着线程数量的增加,当前线程数会增加,直到等于最大线程数。
添加线程规则
如果用流程图来描述如下图
根据上图可知,是否需要增加线程的判断顺序是:
- corePoolSize
- workQueue
- maxPoolSize
增减线程的特点
- 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池。如newFixThreadPool
- 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。
- 通过设置maximumPoolSize为很高的值,例如 Integer.MAX_VALUE。才可以允许线程池容纳任意数量的并发任务。
- 只有在队列填满时才创建多于corePoolSize的线程,所以如果使用的是无界队列(例如LinkedBlockQueue),那么线程数就不会超过corePoolSize。
- ThreadFactory:新的线程是由ThreadFactory创建的,默认使用Executors.defaultThreadFactor(),创建出来的线程都在同一个线程组,拥有同样的NORM PRIORITY优先级并且都不是守护线程。如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程等。
- WorkQueue工作队列
1)直接交换:SynchronousQueue,maxPool需要设置大一些,无队列作为缓冲。
2)无界队列:LinkedBlockingQueue,若处理速度 < 存放队列速度,一直存放,会OOM
3)有界队列:ArrayBlockingQueue
线程池应该手动创建还是自动创建
线程池里的线程数量设定为多少比较合适
停止线程池的正确方式
3. 常见线程池的特点和用法
4.任务太多,怎么拒绝
- 拒绝时机
- 1.当Executor关闭时,提交新任务会被拒绝
- 2.当Executor对最大线程和工作队列使用有限边界并且已经饱和时
四种拒绝策略
- AbortPolicy:直接抛出异常
- Discardpolicy:丢弃
- DiscardOldestPolicy:丢掉最老(早)的任务
- CallerRunsPolicy:谁提交任务,谁跑该线程
5.钩子方法,给线程池加点料
6.实现原理、源码分析
7. 使用线程池的注意点
- 避免任务的堆积
- 避免线程数过度增加
- 排查线程泄露