很多时候,我们为了提高程序响应速度,会将耗时较长的代码交给线程池去异步执行,Java通过Executors提供四种线程池,分别newSingleThreadExecutor
newFixedThreadPool 、newScheduledThreadPool 和newCachedThreadPool ,但这四种并不推荐使用,因此需要自己去自定义线程池,那么,问题就来了,自定义的话,线程池的核心参数如何设置呢?
首先,要明白核心参数有哪些:
1、corePoolSize: 线程池中的常驻核心线程数,在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程。当线程中的线程数达到corePoolSize后,就会把到达的任务放到缓存队列当中。核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
2、maximumPoolSize: 线程池能够容纳同时执行的最大线程,此值必须大于等于1
3、keepAliveTime:多余的空闲线程的存活时间,当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
4、unit: keepAliveTime的单位
5、workQueue: 任务队列,被提交但尚未被执行的任务
6、threadFactory: 表示生成线程池中工作线程的线程工厂,用于创建线程`一般用默认的即可`
7、handler: 拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何拒绝。
8、allowCoreThreadTimeout:是否允许核心线程空闲退出,默认值为false。
其中,最重要的就是核心线程数与最大线程数,因为其直接影响着服务器的性能以及程序的响应速度。下面从两个方面简单解释一下:
性能方面:
线程数的设置的重要目的是为了充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能,所以,要先知道CPU核心数,可以使用 Runtime.getRuntime().availableProcessor() 方法来获取;知道核心数后,还需要明白线程处理的任务失那种类型的:
CPU密集型:大部分时间用来做计算逻辑判断等CPU动作的程序称为CPU密集型任务。该类型的任务需要进行大量的计算,主要消耗CPU资源。 这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。但在通常情况下,会将核心线程数设置为CPU数+1,这个额外的线程可以确保 CPU 的时钟周期不会因为某线程阻塞而浪费;
IO密集型:定义:IO密集型任务指任务需要执行大量的IO操作,涉及到网络、磁盘IO操作,对CPU消耗较少,其消耗的主要资源为IO。 IO 操作的特点就是需要等待,我们请求一些数据,由对方将数据写入缓冲区,在这段时间中,需要读取数据的线程根本无事可做,因此可以把 CPU 时间片让出去,直到缓冲区写满。
既然这样,IO 密集型任务其实就有很大的优化空间了(毕竟存在等待)这时候,核心数数量可以根据**线程数 = CPU 核心数 \* (1 + IO 耗时/ CPU 耗时)**这个公式来设置。
业务方面:
tasks,每秒需要处理的最大任务数量;
tasktime,处理一个任务所需要的时间;
responsetime,系统允许任务最大的响应时间。
为了满足上面的需要,
核心线程数为:corePoolSize=tasks/(1/tasktime),但是tasks不会是固定的,它应该是一个范围,比如100~1000,那么就需要遵循8020原则,即设置为80%情况下系统每秒任务数;
队列长度可以设置为queueCapacity=(corePoolSize/tasktime)*responsetime,队列长度设置过大,会导致任务响应时间过长,所以要根据任务最大的响应时间去设置合适的数值;
最大线程数:当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程,
那么maximumPoolSize=(最大任务数量-queueCapacity)*(1/tasktime))
实际情况中,要结合性能和业务两方面去综合考虑。