1、线程池七大参数说明
参数名称 | 说明 |
---|---|
corePoolSize | 核心线程数,在创建线程池之后,默认情况下线程池中并没有任何线程,而是等待任务来了才创建的,当线程池中的线程数达到了corePoolSize后,新的任务被添加到workQueue,如果不知道设置为多少,建议设置机器cpu的有效核心数:Runtime.getRuntime().availableProcessors(); |
maximumPoolSize | 当活跃线程数等于corePoolSize并且workQueue已满,当线程数小于maximumPoolSize时,就会继续创建线程来执行任务,否则将会调用任务的拒绝策略来拒绝这个任务 |
keepAliveTime | 超过corePoolSize的线程叫“idle thread”,当他keepAliveTime没有分配任务,则会被销毁,0代表空闲立即就销毁 |
unit | keepAliveTime时间的单位 |
workQueue | 任务队列,当活跃线程数据等于corePoolSize时,后续添加的任务会暂时放在队列中,等待分配给空闲的线程执行 |
threadFactory | 线程工厂,用来创建线程的,Executors.defaultThreadFactory()创建默认的线程工厂,其后续创建的线程优先级都是Thread.NORM_PRIORITY。如果我们指定线程工厂,我们可以对产生的线程进行一定的操作。 |
handler | 拒绝执行策略,当线程池的缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略AbortPolicy 默认的,丢弃任务并抛出异常 DiscardPolicy 也是丢弃任务,但是不抛出异常 DiscardOldestPolicy 丢弃队列最前面的,尝试执行新任务(重复此过程) CallerRunsPolicy 由调用线程(通常是main)处理该任务,等到执行时该线程自行处理 |
2、线程池的使用
根据阿里巴巴规范,不推荐外面使用ExecutorService(newFixedThreadPool)的方式创建线程池,因为阻塞队列默认Integer.MAX_VALUE,相当于是一个无界队列,线程池并不会拒绝任务,有可能会触发内存泄漏的风险。
因此推荐采用手动自定义new ThreadPoolExecutor()的方式去创建线程池。
3、最大核心线程数应该如何设置?
就需要考虑业务场景,是CPU密集型任务,还是IO密集型任务
在《Java并发编程实践》,计算线程池的线程数目,是采用基准负载去进行计算
Ncpu = CPU的数量
Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待时间与计算时间的比率Nthreads = Ncpu x Ucpu x (1 + W/C)
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
CPU密集型任务设置为参考值可以设为 Ncpu+1
CPU密集型:进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。
尽可能的利用CPU100%,那为什么多设计多一个线程呢?计算密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。
IO密集型任务设置为参考值可以设置为 2 * Ncpu
IO密集型:99%的时间都花在IO上,花在CPU上的时间很少(网络、磁盘IO、数据库读写)
对于IO密集型应用,假定所有的操作时间几乎都是IO操作耗时,那么 W/C的值就为1,Ucpu 要达到100%利用率。
Nthreads = Ncpu x Ucpu x (1 + 1)=2 * Ncpu
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
实际的情况,还是需要根据业务情况去设定,公式看似合理,实际业务还是需要多方面去考虑,需要考虑整个系统的整体情况,还需要去考虑硬件(CPU、内存、硬盘读写速度、网络状况),对于DB的IO操作还需要去考durid的连接数,QPS等情况,使用多线程虽然能加快程序的运行,但有时候却会成为它最后的一根稻草,合理的慎用多线程。
本文参考博文:CPU 密集型 和 IO密集型 的区别,如何确定线程池大小?_cpu密集型和io密集型区别_醋酸菌HaC的博客-CSDN博客