线程池

线程池的产生

  1. 创建、销毁线程伴随着系统开销、线程是可以重复适用的
  2. 线程并发数量过多,抢占系统资源从而导致阻塞
  3. 对线程要进行管理

Executor

具体实现类是ThreadPoolExecutor类

核心参数

corePoolSize

线程池的基本大小,即 在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超过这个数量的线程。在刚刚创建threadpoolexecutor的时候,线程不会立即启动,而是在有任务提交的时候才会启动,除非调用了prtestartCoreThread/prestartAllcoreThreads事先启动核心线程,没有任务执行的时候,线程池的大小不一定是corepoolSize

maximumPoolSize

线程池允许的最大线程数,线程池中的当前线程数目不会超过这个值,如果队列中任务已满,并且当前线程个数小于maximumpoolsize,那么会创建新的线程来执行任务。

keepAliveTime

如果一个线程处于空闲状态的时间超过该属性,就会因为超时而退出,如果这个线程是核心线程,线程退出取决于allowCoreThreadTimeOut

queueCapacity

阻塞队列 任务队列容量

blockingQueue

1、ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
2、LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列,使用此阻塞队列时maximumPoolSizes就相当于无效
3、SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。可以避免在处理可能具有内部依赖性的请求集时出现锁。

4、PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

rejectedExecutionHandler

当线程池发生两种情况,此时拒绝增加新的任务,将会采用线程池的饱和策略

  • 两种情况
    • 当线程已经达到maxpoolsize时,且队列已满,会拒绝新任务
    • 当线程池被调用shutdown时,会等待线程池里的任务执行完毕再shutdown
  • 饱和策略的类型
    • AbortPolicy 丢弃任务,抛运行时异常RejectedExecutionException 默认策略
    • CallerRunsPolicy 由调用者线程执行任务,如果调用者已关闭,则丢弃
    • DiscardPolicy 忽略
    • DiscardOldestPolicy 从队列中踢出最先进入队列的惹怒五
    • 实现RejectedExecutionHandler接口,可自定义处理器

执行顺序

1
2
3
4
5
1. 当线程数小于核心线程数时,创建线程。
2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3. 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,抛出异常,拒绝任务

线程池的关闭

shutdown 与shutdownnow

常见的几种线程池

Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池

线程池监控

actuator 组件来做线程池的监控。

ThreadPool api

参数设置的方法

任务的类型

  • cpu密集型,
    • 较小的线程池,cpu核心数+1,cpu密集型任务会使cpu使用率很高,若开过多的线程数,只会增加上下文切换的次数,带来额外的开销
  • io密集型
    • cpu使用率不高,可以让cpu在等待io的时候去处理别的任务,充分利用cpu的时间
  • 混合型
    • 分别用不同的线程池去处理,两个任务的结束时间取决与后执行完的任务,适用于两个任务的执行时间相差不大

2N+1

N为cpu核数

根据几个值设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
* 需要根据几个值来决定
- tasks :每秒的任务数,假设为500~1000
- taskcost:每个任务花费时间,假设为0.1s
- responsetime:系统允许容忍的最大响应时间,假设为1s
* 做几个计算
- corePoolSize = 每秒需要多少个线程处理?
* threadcount = tasks/(1/taskcost) =tasks*taskcout = (500~1000)*0.1 = 50~100 个线程。corePoolSize设置应该大于50
* 根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可
- queueCapacity = (coreSizePool/taskcost)*responsetime
* 计算可得 queueCapacity = 80/0.1*1 = 80。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
* 切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
- maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
* 计算可得 maxPoolSize = (1000-80)/10 = 92
* (最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数
- rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
- keepAliveTime和allowCoreThreadTimeout采用默认通常能满足

源码分析

线程池的创建

添加任务

任务的获取

任务的执行

https://blog.csdn.net/tbdp6411/article/details/78443732