gogogo
管理员
管理员
  • UID25
  • 粉丝0
  • 关注0
  • 发帖数1384
阅读:2362回复:2

Java 线程池参数及配置

楼主#
更多 发布于:2023-08-21 15:51


线程池-线程池参数及配置

在实际项目中线程的应用都会使用线程池来管理,线程池的常用参数及配置学习记录。

一、线程池
       在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在 Java 中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。


      如果并发的线程数多,并且每个线程都是执行一个时间很短的任务就结束了,这样会造成频繁的创建和销毁线程从而导致降低系统的效率。


      线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。


      那么问题来了,有没有办法可用复用创建好的线程呢,也就是线程执行完一个任务后,不被销毁,继续执行其他的任务?


      用线程池来管理多个线程,复用空闲线程,减少线程的创建和销毁,提升系统性能。
————————————————
1、线程池的优势
(1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。
————————————————
gogogo
管理员
管理员
  • UID25
  • 粉丝0
  • 关注0
  • 发帖数1384
沙发#
发布于:2023-08-21 15:54


二、线程池常用参数 ThreadPoolExecutor




public ThreadPoolExecutor(int corePoolSize,
                            int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }




1. corePoolSize:核心线程数
1.1 核心线程会一直存活,即使没有任务需要执行。

1.2 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。

1.3 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。

1.4 可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。

2. queueCapacity:任务队列容量(阻塞队列)
2.1  当核心线程数达到最大时,新任务会放在队列中排队等待执行。

1. corePoolSize:核心线程数
1.1 核心线程会一直存活,即使没有任务需要执行。

1.2 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。

1.3 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。

1.4 可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。

2. queueCapacity:任务队列容量(阻塞队列)
2.1  当核心线程数达到最大时,新任务会放在队列中排队等待执行。

6.1 两种情况会拒绝处理任务:

(1)当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务。

(2)当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常。

6.2 ThreadPoolExecutor类有几个内部实现类来处理这类情况-handle饱和策略:

(1)AbortPolicy 丢弃任务,抛运行时异常。

(2)CallerRunsPolicy 执行任务。

(3)DiscardPolicy 忽视,什么都不会发生。

(4)DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。实现RejectedExecutionHandler接口,也可自定义处理器。
————————————————


三、ThreadPoolExecutor执行过程
1.当线程数小于核心线程数时,创建线程。

2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。

3.当线程数大于等于核心线程数,且任务队列已满。

(1)若线程数小于最大线程数,创建线程。

(2)若线程数等于最大线程数,抛出异常,拒绝任务。
————————————————




四、Java中4种线程池
Java通过Executors提供四种线程池,分别为:

4.1  newCachedThreadPool
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)

4.2  newFixedThreadPool
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)

4.3  newScheduledThreadPool
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newScheduledThreadPool:适用于执行延时或者周期性任务。

4.4  newSingleThreadExecutor
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。

五、Java线程池参数配置
1、线程池的默认值
corePoolSize=1

queueCapacity=Integer.MAX_VALUE

maxPoolSize=Integer.MAX_VALUE

keepAliveTime=60s

allowCoreThreadTimeout=false

rejectedExcutionHandler=AbortPolicy()

2、参数配置
配置参数时需要考虑  CPU密集型任务 、 IO密集型任务 、内存使用率 、下游系统抗并发的能力

配置参数:
CPU密集型 CPU的核数+1
IO密集型 一般配置 2*CPU的核数
参考公式(某大厂配置):
CPU核数/(1-阻塞系数) 阻塞系数在0.8~0.9之间
比如8核CPU 8/(1-0.9) = 80个线程数


————————————————
版权声明:本文为CSDN博主「菜鸟柱子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xinpz/article/details/110132365
gogogo
管理员
管理员
  • UID25
  • 粉丝0
  • 关注0
  • 发帖数1384
板凳#
发布于:2023-08-22 10:14


那些年我们踩到过的坑(一):为ThreadPoolExecutor 指定RejectedExecutionHandler需要注意的坑


昨天下午公司的短信发送服务挂掉,查日志发现有些短信服务提供商的服务器time out。马上联系对方,确认服务已经恢复正常,我们立马重启服务,恢复正常。

我们的短信服务是起一个线程T1从redis list去拿消息,然后创建一个发送短信的任务线程扔到线程池里执行,每一个发送短信的任务都会连接服务商的服务,time out就是从这里抛出来的,可是连接time out怎么能导致我们的服务挂掉呢? 还好当时做了thread dump,分析了下dump 文件,发现线程T1挂掉了,看代码发现该线程的run方法块里面只catch住了Exception,极有可能是有Throwable的错误抛了出来,导致该线程意外终止。我当时能够想到的有OutOfMemmory 跟 StackOverflow,经过分析定位到以下我们封装的线程池。





private ThreadPoolExecutor threadPoolTaskExecutor = new ThreadPoolExecutor(30, 100, 30,
            TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), new ThreadFactoryBuilder("SmsServiceManager-threadPoolTaskExecutor"),
            new RejectedExecutionHandler() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    if (!executor.isShutdown()) {
                        executor.execute(r);
                    }
                }
 });




注意该类指定了RejectedExecutionHandler,如果任务被reject那么将任务继续添加到线程池。WTF!!!!真暴力啊,线程池拒绝了你的任务,你不做任何处理又把该任务扔进线程池,这就是stack over flow的原因!!!线程池一共有30个worker线程,当时正好某些服务商的短信服务出问题,所以这30个worker线程一直卡在连接对方服务上,当线程池中任务的数量超过100个,那么后来的任务将会被线程池reject,而你被reject后将该任务再一次扔进线程池,产生死循环直到线程T1栈溢出。
最后我们的解决方案是:
1. 在线程T1的run方法块里面catch住Throwable。
2. 任务被线程池reject后将消息重新放到redis队列里。

这两天刚刚接手短信这块,坑很多,且行且小心。
游客


返回顶部