Java中的线程池
为什么使用线程池?
- 反复创建线程开销较大
- 过多的线程会占用太多的内存
使用线程池的优点:
- 降低资源消耗(重复利用已创建的线程)
- 提高响应速度
- 集中控制
线程池的创建:
new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,workQueue,threadFactory,Handler);
参数意义:
- corePoolSize 核心线程数:如果当前运行的线程小于corePoolSize时,则创建新线程来执行任务
- 如果运行的线程等于或大于corePoolSize 则将任务加入队列
- 如果队列已满,则创建新的线程,线程总数不能超过 maximumPoolSize
- 如果队列已满,且线程数大于或等于maximumPoolSize 则拒绝任务
- keepAliveTime 超时时间:如果线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime ,他们就会被终止
巧妙设置参数
- 通过设置corePoolSize与maximumPoolSize相同,可以设置固定大小的线程池
- 通过设置maximumPoolSize为很高的值,例如Ingteger.MAX _VALUE,在理论上可以允许线程池可以容纳任意数量的并发任务
- 只有在队列填满时才创建多于corePoolSize的线程,所以如果使用的是无界队列(例如LinkedBlockingQueue),那么线程数不会超过corePoolSize
工作队列
- ArrayBlockingQueue:一个基于数组结构的有界阻塞队列,此队列按照先进先出原则对元素进行排序
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按照先进先出原则对元素进行排序。吞吐量高于ArrayBlockingQueue
- SynchronusQueue:一个不存储元素的阻塞队列。吞吐量高于LinkedBlockingQueue
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列
Executor框架
Executors可以创建3种类型的线程池
- FixedThreadPool:适用于为了满足资源管理的需求,而需要限制线程数量的应用场景。内部工作队列为LinkedBlockingQueue,容量为Integer.MAX_VALUE. 所以当请求越来越多,而且无法及时处理完毕时,也就是请求堆积时,会容易造成占用大量的内存,可能导致OOM
- SingleThreadExecutor:固定容量为1的线程池。适用于保证顺序执行各个任务,并且在任意时间内,不会有多线程是活动的。内部工作队列也为LinkedBlockingQueue,故缺点也与上述相同
- CachedThreadPool:适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。最大线程数为Integer.MAX_VALUE,这可能会导致创建数量非常多的线程
- ScheduledThreadPool 支持周期性的任务执行的线程池
线程池中的线程数目设定为多少比较合适?
- CPU密集型(加密、计算hash等):最佳线程数为CPU核心数的1-2倍左右
- 耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于CPU核心数很多倍,以JVM线程监控显示繁忙情况为依据,保证线程空闲可以衔接上。
- 线程数=CPU核心数*(1+平均等待时间/平均工作时间)
线程池状态
- RUNNING:接受新任务并处理排队任务
- SHUTDOWN:不接受新任务,但处理排队任务
- STOP:不接受新任务,也不处理排队任务,并中断正在运行的任务
- TIDYING:所有的任务 都已终止,线程会转换到TIDYING状态,并将运行terminate() 钩子方法
- TERMINATED:terminate()运行完成
线程池的操作
execte:提交任务
shutdown 停止线程
IsShutdown 检测是否执行了shutdown方法
IsTerminated 检测线程是否完全停止
AwatitTermination:检测一段时间内线程是否完全终止
ShutdownNow:关闭所有线程(为执行中的线程执行interrupt通知,并将队列中未执行完毕的线程存入返回的List数组中)
拒绝任务
拒绝时机
- 当Executor关闭时,提交新任务会被拒绝
- 当Executor 的线程容量和工作队列已经饱和时
拒绝策略
- AbortPolicy 拒绝并返回错误信息
- DiscardPolicy 偷摸拒绝 不返回错误信
- DiscardOldestPolicy 拒绝队列中最老的任
- CallerRunsPolicy 将该任务抛给主线程执行
线程池的组成部分
- 线程池管理器
- 工作队列
- 任务队列
- 任务接口
使用线程池的注意点
- 避免任务堆积
- 避免线程数过度增加
- 排查线程泄露