受限于硬件、内存和性能,我们不可能无限制的创建任意数量的线程,每一台机器允许的最大线程是一个有界值。因此ThreadPoolExecutor管理的线程数量是有界的。线程池就是用这些有限个数的线程,去执行提交的任务。但是对于多用户、高并发的应用来说,提交的任务数量非常巨大,会比允许的最大线程数多很多。为了解决这个问题,必须要引入排队机制,或者是在内存中,或者是在硬盘等容量很大的存储介质中。Java的ThreadPoolExecutor只支持任务在内存中排队,通过BlockingQueue暂存还没有来得及执行的任务。
线程的管理是比较复杂的,这会涉及线程数量、等待/唤醒、同步/锁、线程创建和死亡等问题。
ThreadPoolExecutor中的这七大参数,共同负责着线程池中的线程的创建和销毁
- int corePoolSize
- int maximumPoolSize
- long keepAliveTime
- TimeUnit unit
- BlockingQueue workQueue
- ThreadFactory threadFactory
- RejectedExecutionHandler handler
方式一 根据计算任务的性质
-
任务的性质
CPU密集型任务
IO密集型任务
混合型任务
-
任务的优先级
高
中
低
-
任务的执行时间
长
中
短
-
任务的依赖性
是否依赖其他系统资源
如,数据库连接等
性质不同的任务可以交给不同规模的线程池执行
CPU密集型任务 应配置尽可能小的线程 如, 配置 CPU个数+1 的线程数
IO密集型任务 应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量 如, 配置 两倍CPU个数+1
混合型的任务 如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了
其他系统资源有依赖的任务,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU
合理设置线程池值大小,需要结合系统实际情况大量的尝试和比较去得出合理的设置大小
最佳线程数目 = ((线程等待时间 + 线程CPU时间)/ 线程CPU时间 )* CPU数目
如, 平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8
那么根据上面这个公式估算得到:((0.5 + 1.5) / 0.5) * 8 = 32这个公式进一步转化为:
最佳线程数目 = ((线程等待时间 / 线程CPU时间) + 1)* CPU
结论:
- 线程等待时间所占比例越高,需要越多线程
- 线程CPU时间所占比例越高,需要越少线程
以上公式与CPU和IO密集型任务设置线程数基本吻合。
-
高并量发、任务执行时间短的业务怎样设置线程池?
CPU核数+1 减少线程上下文的切换
-
高并量发、任务执行时间长的业务怎样设置线程池?
解决这种类型任务的瓶颈往往不在于线程池而在于整体架构的设计
1、看看这些业务里面某些数据是否能做缓存
2、是否需要增加服务数量
3、考虑线程池的设置
4、能否使用中间件对任务进行拆分和解耦 -
并发不高、任务执行时间长的业务怎样设置线程池?
IO密集型的任务 加大线程池中的线程数目,让CPU处理更多的业务
计算密集型任务 CPU核数+1 减少线程上下文的切换