线程池最佳实践:线程池使用的注意事项和参数设置

线程池使用时可以从以下几个方面来考虑;

1.避免用Executors 的创建线程池

Executors常用方法有以下几个:

1)newCachedThreadPool():创建一个可缓存的线程池,调用 execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到线程池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。CachedThreadPool适用于并发执行大量短期耗时短的任务,或者负载较轻的服务器;

2)newFiexedThreadPool(int nThreads):创建固定数目线程的线程池,线程数小于nThreads时,提交新的任务会创建新的线程,当线程数等于nThreads时,提交新的任务后任务会被加入到阻塞队列,正在执行的线程执行完毕后从队列中取任务执行,FiexedThreadPool适用于负载略重但任务不是特别多的场景,为了合理利用资源,需要限制线程数量;

3)newSingleThreadExecutor() 创建一个单线程化的 Executor,SingleThreadExecutor适用于串行执行任务的场景,每个任务按顺序执行,不需要并发执行;

4)newScheduledThreadPool(int corePoolSize) 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代 Timer 类。ScheduledThreadPool中,返回了一个ScheduledThreadPoolExecutor实例,而ScheduledThreadPoolExecutor实际上继承了ThreadPoolExecutor。从代码中可以看出,ScheduledThreadPool基于ThreadPoolExecutor,corePoolSize大小为传入的corePoolSize,maximumPoolSize大小为Integer.MAX_VALUE,超时时间为0,workQueue为DelayedWorkQueue。实际上ScheduledThreadPool是一个调度池,其实现了schedule、scheduleAtFixedRate、scheduleWithFixedDelay三个方法,可以实现延迟执行、周期执行等操作;

5)newSingleThreadScheduledExecutor() 创建一个corePoolSize为1的ScheduledThreadPoolExecutor;

6)newWorkStealingPool(int parallelism)返回一个ForkJoinPool实例,ForkJoinPool 主要用于实现"分而治之"的算法,适合于计算密集型的任务。

Executors类看起来功能比较强大、用起来还比较方便,但存在如下弊端:

1)FiexedThreadPool和SingleThreadPool任务队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;

2)CachedThreadPool和ScheduledThreadPool允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM;

使用线程时,可以直接调用 ThreadPoolExecutor 的构造函数来创建线程池,并根据业务实际场景来设置corePoolSize、blockingQueue、RejectedExecuteHandler等参数。

2.避免使用局部线程池

使用局部线程池时,若任务执行完后没有执行shutdown()方法或有其他不当引用,极易造成系统资源耗尽。

3.合理设置线程池参数

在工程实践中,通常使用下述公式来计算核心线程数:

nThreads=(w+c)/cn u=(w/c+1)nu

其中,w为等待时间,c为计算时间,n为CPU核心数(通常可通过 Runtime.getRuntime().availableProcessors()方法获取),u为CPU目标利用率(取值区间为[0, 1]);在最大化CPU利用率的情况下,当处理的任务为计算密集型任务时,即等待时间w为0,此时核心线程数等于CPU核心数。

上述计算公式是理想情况下的建议核心线程数,而不同系统/应用在运行不同的任务时可能会有一定的差异,因此最佳线程数参数还需要根据任务的实际运行情况和压测表现进行微调。

4.增加异常处理

为了更好地发现、分析和解决问题,建议在使用多线程时增加对异常的处理,异常处理通常有下述方案:

在任务代码处增加try...catch异常处理;

如果使用的Future方式,则可通过Future对象的get方法接收抛出的异常;

为工作线程设置setUncaughtExceptionHandler,在uncaughtException方法中处理异常。

5. 优雅关闭线程池

java 复制代码
public void destroy() {        
try {            
poolExecutor.shutdown();            
if (!poolExecutor.awaitTermination(AWAIT_TIMEOUT, TimeUnit.SECONDS)) {       
         poolExecutor.shutdownNow(); 
                    }     
                       } catch (InterruptedException e) {            // 如果当前线程被中断,重新取消所有任务 
           pool.shutdownNow();           
                        // 保持中断状态     
                               Thread.currentThread().interrupt();       
                                }   
                                 }

为了实现优雅停机的目标,我们应当先调用shutdown方法,调用这个方法也就意味着,这个线程池不会再接收任何新的任务,但是已经提交的任务还会继续执行。之后我们还应当调用awaitTermination方法,这个方法可以设定线程池在关闭之前的最大超时时间,如果在超时时间结束之前线程池能够正常关闭则会返回true,否则,超时会返回false。通常我们需要根据业务场景预估一个合理的超时时间,然后调用该方法。

如果awaitTermination方法返回false,但又希望尽可能在线程池关闭之后再做其他资源回收工作,可以考虑再调用一下shutdownNow方法,此时队列中所有尚未被处理的任务都会被丢弃,同时会设置线程池中每个线程的中断标志位。shutdownNow并不保证一定可以让正在运行的线程停止工作,除非提交给线程的任务能够正确响应中断。

相关推荐
CoderJia程序员甲几秒前
重学SpringBoot3-Spring WebFlux之HttpHandler和HttpServer
java·spring boot·reactor·1024程序员节
chuk.8 分钟前
【JAVA】利用钉钉自定义机器人监控NACOS服务,实现实时下线通知
java·机器人·钉钉
weixi_kelaile5208 分钟前
ai智能语音电销机器人可以做哪些事情?
java·linux·服务器·人工智能·机器人·云计算·腾讯云
hummhumm11 分钟前
Oracle 第13章:事务处理
开发语言·数据库·后端·python·sql·oracle·database
@尘音14 分钟前
QT——记事本项目
开发语言·qt
书鸢123616 分钟前
力扣每日一题合集
java·算法·leetcode
童先生16 分钟前
python 用于请求chartGpt DEMO request请求方式
开发语言·python
qing_04060317 分钟前
C++——string的模拟实现(上)
开发语言·c++·string
魔道不误砍柴功18 分钟前
Java 中 String str = new String(“hello“); 里面创建了几个对象?
java·开发语言·string·new
菜菜-plus1 小时前
微服务技术,SpringCloudAlibaba,Redis,RocketMQ,Docker,分库分表
java·spring boot·redis·spring cloud·docker·微服务·java-rocketmq