线程池
线程池原理及其七大核心参数含义
线程池的核心思路是:复用线程。避免了来一个任务就new,不需要就销毁的开销
流程:
任务来了,先判断当前线程数量是否小于corePoolSize
如果小于,则创建核心线程立即执行任务
如果核心线程数已满,则尝试将任务加入到阻塞队列
如果阻塞队列满了,就创建非核心线程执行任务;未满继续等待。
如果非核心线程数也达到最大,则触发拒绝策略
线程执行完毕后,或继续从阻塞队列中获取任务执行
非核心线程超过空闲时间keepAliveTime会被销毁
核心线程默认长期存活,不会销毁
简单概括就是:
优先使用核心线程
核心线程满后使用队列
队列满后扩容线程
达到最大线程数后拒绝执行任务
核心参数的实际含义
ThreadPoolExecutor的5个参数:核心线程数,最大线程数,阻塞队列,拒绝策略,空闲存活时间
- corePoolSize:核心线程数。即使这信啊线程空闲也不会被回收
- maximumPoolSize:最大线程数,线程池能创建线程的最大值。核心线程满了,队列满了才会创建
- keepAliveTime和unit:空闲线程的存活时间。超过核心线程的那部分,空闲时间超过就会被销毁
- workQueue:阻塞队列
- handler:拒绝策略,队列满了,线程也到顶了
线程池四大种类及特点
fixed :固定线程数线程池
特点:
- 核心线程数固定
- 最大线程数固定
- 使用无界队列
适用场景
- 稳定
- 长期
cached :可缓存线程池
特点:
- 核心线程数为0
- 线程可无限扩容
- 空闲线程会回收
适用场景:
- 大量短时间异步任务
- 突发并发场景
问题:线程数会无限增长
single :单线程线程池
特点:
- 只有一个工作线程
- 保证任务顺序
- 适用无界队列
适用场景:
- 顺序消费
schedule :定时线程池
特点:
- 支持延迟任务
- 支持周期任务
- 基于DelayQueue
适用场景
- 延迟执行
- 周期执行
如何合理设置Java线程池的线程数
关键在于CPU密集型 还是I/O密集型 。
CPU密集型:线程数=CPU核心数+1
I/O密集型:线程数=CPU核心数*2
实际方法
- 先用上述公式跑起来
- 做压测;逐步加大并发,观察CPU利用率,平均响应时间,队列堆积数
- CPU占用率70%-80%健康。太低说明线程数可以加,太高说明瓶颈在CPU
- 观察队列堆积,如果队列经常满说明线程数不够或者下游扛不住
ThreadLocal原理
先看Thread的结构:
Thread
↓
ThreadLocalMap
↓
(ThreadLocal -> value)
回答要点:Thread内部维护了一个ThreadLocalMap,其中ThreadLocal就是key,线程私有数据作为value
实现:每个线程数据隔离
ThreadLocalMap 结构(内存泄露)
不是普通的HashMap
内部是:Entry\[\] table 数组结构
这导致了key是弱引用
作用:当hreadLocal 对象不用了,因为是弱引用,可以被GC(垃圾回收)。避免了key永远无法释放
问题:内存泄露
因为:key会被GC回收,但是value作为强引用不会被回收。导致value会一直挂在线程的ThreadLocalMap 里。导致内存泄露
解决方法:通常在finally中remove()