线程池的类关系
线程池的继承关系:
- Executor:接口,只有一个方法 executor()
- ExecutorService:接口,继承了 Executor 接口,增加了几个管理线程池的方法
- ThreadPoolExecutor:线程池的的实现类
创建线程池的工具类:Executors
线程池的创建
线程池支持两种方式:
- 通过 ThreadPoolExecutor 构造方法自定义
- 通过 Executors 工具类创建线程池
自定义线程池
通过 ThreadPoolExecutor 的构造方法自定义线程池:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit, // 延时单位
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:int,核心线程数,线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再去创建新线程执行任务
- maximumPoolSize:int,最大线程数,线程池所能持有线程的最大数量
- keepAliveTime:long,保持存活时间,当线程池的线程数多于 corePoolSize ,那么多余的线程空闲时间超过 keepAliveTime 时,它们就会被终止
- workQueue:BlockingQueue,任务存储队列,常见的工作队列有三种
- 直接交接:SynchronousQueue
- 无界队列:LinkedBlockingQueue
- 有界队列:ArrayBlockingQueue
- threadFactory:ThreadFactory,当线程池需要新的线程的时候,会使用 threadFactory 来生成新的线程
- 新的线程默认是由 ThreadFactory 创建的,默认使用 Executors.defaultThreadFactory() ,创建出来的线程都在同一个线程组,拥有同样的 NORM_PRIORITY 优先级并且均非守护线程
- 自定义 ThreadFactory 时,就可以指定线程名、线程组、线程优先级、是否为守护线程等
- Handler:RejectedExecutionHandler,当线程池无法接受新任务的拒绝策略
增减线程的特点:
- 通过设置 corePoolSize 和 maxPoolSize 相同,就可以创建固定大小的线程池
- 线程池希望保持较小的线程数,只有在负载变得很大时才增加它
- 通过设置 maxPoolSize 为很大的值,如 Integer.MAX_VALUE ,可以允许线程池容纳任意数量的并发任务
- 只有在队列填满时才创建多于 corePoolSize 的线程,所以使用无界队列时(如 LinkedBlockingQueue ),那么线程数就不会超过 corePoolSize
线程池的定义:建议自定义
- 根据业务场景,设置合适的线程池参数
- 设置合适的线程数量:
- CPU 密集型(加密、计算 hash 等):最佳线程数为 CPU 核心数的 1-2 倍
- IO 密集型(读取数据库、文件、网络读写等):最佳线程数一般会大于 CPU 核心数很多倍,以 JVM 线程监控显示繁忙情况为依据,保证线程空闲可以衔接上,参考 Brain Goetz 推荐的计算方法:线程数 = CPU 核心数 * (1 + 平均等待时间/平均工作时间)
通过 Executors 工具类创建
- Executors.newFixedThreadPool():
- 特点:创建固定大小的线程池(corePoolSize == maxPoolSize),使用无界队列 LinkedBlockingQueue
- 缺点:由于 LinkedBlockingQueue 是没有容量上限的,当请求数越来越多并且无法及时处理完毕时,即请求堆积,会容易占用大量的内存,导致 OOM
- Executors.newSingleThreadExecutor():
- 特点:只有一个线程,使用无界队列 LinkedBlockingQueue
- 缺点:同上,请求堆积,占用大量的内存,导致 OOM
- Executors.newCachedThreadPool():new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,SynchronousQueue
()) - 特点:可缓存线程池;无界线程池,能够自动回收多余线程;使用的是 SynchronousQueue(只能持有一个任务)
- 缺点:maxPoolSize被设置成了 Integer.MAX_VALUE,可能会创建大量的线程,甚至导致 OOM
- Executors.newScheduledThreadPool()
- 特点:支持定时及周期性任务执行的线程池
线程池使用
使用线程池的注意点:
- 避免任务堆积
- 避免线程数过度增加
- 排查线程泄露
线程复用:ThreadPoolExecutor.Worker.runWorker()方法,通过使用当前线程执行 run() 方法实现线程复用
线程添加规则
线程添加规则:
- 如果线程数小于 corePoolSize,即使其他工作线程处于空闲状态,也会创建一个新线程来运行新任务
- 如果线程数等于(或大于) corePoolSize 但少于 maxPoolSize ,则将任务放入队列
线程池的拒绝策略
线程池拒绝时机:
- 当 Executor 关闭时,提交新任务会被拒绝
- 当 Executor 的工作队列已满,线程已经达到最大线程时
线程池的拒绝策略:
- AbortPolicy:直接抛出异常
- DiscardPolicy:直接丢弃
- DiscardOldestPolicy:丢弃旧任务,保留新任务
- CallerRunsPolicy:将任务交给提交的线程去运行
关闭线程池
停止线程池:
- shutdown:停止线程池,拒绝接受新任务,但会继续执行任务队列中的任务
- isShutDown:判断线程池是否停止,即是否还能接受新任务,并不是线程池完全停止
- isTerminated:判断线程池是否完全停止
- awaitTermination:检测线程池是否完全停止,延时检测
- shutdownNow:立即关闭线程池,中断正在运行的线程,返回队列中的任务