线程池种类以及参数设置问题

JDK1.5中引入了强大的concurrent包,线程池的实现ThreadPoolExcutor。

线程池种类

    通常开发者都是利用Executors提供的通用线程池创建方法,去创建不同配置的线程池,主要区别在于ExecutorService类型或者不同的初始参数。

    Executors目前提供了五种不同的线程池创建配置:

  • newCachedThreadPool(),它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过60秒,则被终止并移出缓存;长时间闲置时,这种线程池并不会消耗什么资源,其内部使用SynchronousQueue作为工作队列;
  • newFixedThreadPool(),创建固定大小的线程池,背后使用的是无界的工作队列,任何时候最多有nThreads个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目nThreads。
  • newSingleThreadExcuteor(),它的特点在于工作线程数目被限制为1,操作一个无界的工作队列,所以保证了所有任务都是被顺序执行的,最多只会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。
  • newSingleThreadScheduledExcutor()和newScheduleThreadPool(int corePoolSize),创建的是个ScheduledExcutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。
  • newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java 8才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。

阻塞队列(runnableTaskQueue)

  • ArrayBlockingQueue:一个基于数组结构的有界阻塞队列,此队列按照先进先出原则对元素进行排序;
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,按照先进先出对元素进行排序。吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Excutors.newFixedThreadPool()使用了这个队列;
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。吞吐量要高于LinkedBlockingQueue。静态工厂方法Excutors.newCachedThreadPool()使用了这个队列;
  • PriorityBlockingQueue:一个具有优先级的无界阻塞队列。

系统负载

    参数的设置跟系统的负载有直接的关系,系统负载相关参数:

  • tasks,每秒需要处理的最大任务数
  • tasktime,处理每个任务所需要的时间
  • responsetime,系统允许任务最大的响应时间,比如每个任务的响应时间不得超过2秒

ThreadPoolExcutor类可设置的参数有:

1.corePoolSize:

    核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理;

    核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。

    每个任务需要tasktime秒处理,则每个线程每秒可以处理1/tasktime个任务。系统每秒有tasks个任务,所以需要的线程数为tasks/(1/tasktime),即tasks*tasktime个线程数。

    假设系统每秒任务数为100-1000,每个任务耗时0.1秒,那么需要10-100个线程, 核心线程数应设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%情况下每秒任务数小于200,最多时为1000,那么可以设置为20。

2.maxPoolSize:

    当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数等于最大线程数,则已经超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。

    当系统负载达到最大值的时候,核心线程数已经无法按时处理完所有任务,这时候就需要增加线程。每秒200个任务需要20个线程,当每秒达到1000个任务的时候,需要(1000-queueCapacity)*(20/200)=60个线程,可以将最大线程数设置为60。

3.keepAliveTime:

    当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量达到corePoolSize。如果allowCoreThreadTimeout被设置为true,则所有线程均会退出直到线程数量为0.

4.allowCoreThreadTimeout:

    是否允许核心线程空闲退出。

5.queueCapacity:

    任务队列容量。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。

    任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为:

(corePoolSize/tasktime)*responsetime:20/0.1*2=400,即队列长度可以设置为400。

    队列长度设置过大,会导致任务响应时间过长,切忌以下写法:

LinkedBlockingQueue queue=new LinkedBlockingQueue();

    这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也随之陡增。

6.RejectedExcutionHandler:饱和策略

当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理。这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。JAVA提供了4中策略:

  • AbortPolicy:直接抛出异常
  • CallerRunsPolicy:只用调用所在的线程运行任务
  • DiscardOledestPolicy:丢弃队列里最近的一个任务,并执行当前任务
  • DiscardPolicy:不处理,丢弃掉

    以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间(tasktime)。

参考:https://blog.csdn.net/zhouhl_cn/article/details/7392607

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线程池参数设置涉及到线程数量、任务队列大小以及拒绝策略等几个关键参数。以下是线程池参数设置的一些准则: 1. 根据系统资源和任务特性合理设置线程数量:线程数量过多会导致资源浪费,过少则可能无法充分利用系统资源。可以根据系统的 CPU 核数和任务类型来决定线程数量,一般推荐设置为 CPU 核数的倍数。 2. 设置合适的任务队列大小:任务队列用于存放等待执行的任务,如果队列太小,可能导致任务被丢弃;如果队列太大,则可能导致内存占用过高。根据任务的平均处理时间、任务到达率和系统的可接受负载情况来设置任务队列大小。 3. 选择适当的拒绝策略:当任务队列已满时,新任务无法加入队列。常见的拒绝策略有:抛出异常、直接丢弃、丢弃最旧的任务或者调用者自己处理。根据业务需求和系统稳定性要求来选择合适的拒绝策略。 4. 考虑任务执行时间:如果任务执行时间较长,可能会导致线程池中的线程长时间被占用,无法及时响应新的任务。可以根据任务执行时间和任务到达率来合理设置线程数量,以充分利用系统资源。 5. 动态调整参数:根据实际情况,动态调整线程池的参数是很重要的。可以通过监控系统负载、任务队列长度等指标,实时调整线程数量和任务队列大小,以适应系统的变化。 总之,线程池参数设置需要考虑系统资源、任务特性和业务需求等因素,合理设置参数可以提高系统性能和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值