java 线程池 自用

ThreadPoolExecutor 的核心参数

1. corePoolSize:核心线程数

  • 核心线程会一直存活,及时没有任务需要执行

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

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

2. queueCapacity:任务队列容量(阻塞队列)

  • 当核心线程数达到最大时,新任务会放在队列中排队等待执行

3. maxPoolSize:最大线程数

  • 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务

  • 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常

4. keepAliveTime:线程空闲时间

  • 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize

  • 如果allowCoreThreadTimeout=true,则会直到线程数量=0

5. allowCoreThreadTimeout:允许核心线程超时

6. rejectedExecutionHandler:任务拒绝处理器

  • 两种情况会拒绝处理任务:
  • 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
  • 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
  • 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常

  • ThreadPoolExecutor类有几个内部实现类来处理这类情况:

  • AbortPolicy 丢弃任务,抛运行时异常
  • CallerRunsPolicy 执行任务
  • DiscardPolicy 忽视(丢弃),什么都不会发生
  • DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
  • 实现RejectedExecutionHandler接口,可自定义处理器

ThreadPoolExecutor执行顺序

如果没有空闲的线程执行该任务

  1. 当线程数小于核心线程数时,创建线程。
  2. 当线程数大于等于核心线程数(corePoolSize),且任务队列未满时,将任务放入任务队列。
  3. 当线程数大于等于核心线程数(corePoolSize),且任务队列已满
    1. 若线程数小于最大线(maximumPoolSize)程数,创建线程
    2. 若线程数等于最大线程数(maximumPoolSize),抛出异常,拒绝任务(根据拒绝策略处理)

ThreadPoolExecutor如何设置参数

计算公式分为:

CPU密集型(CPU-bound)

CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading 很高。
在多重程序系统中,大部分时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部分时间用在三角函数和开根号的计算,便是属于CPU bound的程序。
CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。
线程数一般设置为:
线程数 = CPU核数+1 (现代CPU支持超线程)

IO密集型(I/O bound)

IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。
I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力
线程数一般设置为:
线程数 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

1、默认值

corePoolSize=1
maxPoolSize=Integer.MAX_VALUE
keepAliveTime=60s
allowCoreThreadTimeout=false
queueCapacity=Integer.MAX_VALUE
rejectedExecutionHandler=AbortPolicy()

需要根据几个值来决定
  tasks :每秒的任务数
  tasktime:每个任务花费时间
  responsetime:系统允许容忍的最大响应时间,比如每个任务的响应时间不得超过2秒。

2、默认值
corePoolSize:
每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即task * stasktime个线程数。假设系统每秒任务数为100 -1000,每个任务耗时0.1秒,则需要1000.1至10000.1,即10~100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下任务数小于200,最多时为1000,则corePoolSize可设置为20。

queueCapacity:
任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)*responsetime: (20/0.1)*2=400,即队列长度可设置为400。
队列长度设置过大,会导致任务响应时间过长,切忌以下写法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。

关于网上说这个公式有问题,应该是(corePoolSize/tasktime)*responsetime-corePoolSize。400-20=380,不然队列的最后20个会超时。(PS:还没有测试过)

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

keepAliveTime:
线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。

allowCoreThreadTimeout:
默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。

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

总结基于实践有以下几个配置公式:
1、corePoolSize = 每秒需要多少个线程处理=tasks/(1/tasktime) =tasks*tasktime
2、queueCapacity = (coreSizePool/tasktime)*responsetime
3、maxPoolSize = (max(tasks)- queueCapacity)/(1/tasktime)

queueCapacity任务队列

java中提供了四种方式:直接提交队列、有界任务队列、无界任务队列、优先任务队列

1.SynchronousQueue

使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;
静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。

2.ArrayBlockingQueue

使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。

3.LinkedBlockingQueue

使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

4.PriorityBlockingQueue

PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

5.LinkedBlockingDeque

LinkedBlockingDeque 是一个基于链表的双端队列。与LinkedBlockingQueue不同,是 LinkedBlockingDeque 既可以从尾部插入/取出元素,还可以从头部插入元素/取出元素。

6.LinkedTransferQueue

LinkedTransferQueue 也是一个无限队列,它除了具有一般队列的操作特性外(先进先出),还具有一个阻塞特性:LinkedTransferQueue可以由一对生产者/消费者线程进行操作,当消费者将一个新的元素插入队列后,消费者线程将会一直等待,直到某一个消费者线程将这个元素取走,反之亦然。

Handler拒绝策略

handler :表示当 workQueue 已满,且池中的线程数达到 maximumPoolSize 时,线程池拒绝添加新任务时采取的策略。

策略备注
ThreadPoolExecutor.AbortPolicy()抛出RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy()由向线程池提交任务的线程来执行该任务
ThreadPoolExecutor.DiscardPolicy()抛弃当前的任务
ThreadPoolExecutor.DiscardOldestPolicy()抛弃最旧的任务(最先提交而没有得到执行的任务)

Executors 线程池工具类

为了防止使用者错误搭配 ThreadPoolExecutor 构造函数的各个参数以及更加方便简洁的创建ThreadPoolExecutor对象,JavaSE中又定义了Executors类,Eexcutors类提供了创建常用配置线程池的方法。以下是Executors常用的三个创建线程池的源代码。
从源码中可以看出,Executors 间接的调用了重载的ThreadPoolExecutor 构造函数,并帮助用户根据不同的应用场景,配置不同的参数。

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0,Integer.MAX_VALUE, 
    					 60L, TimeUnit.SECONDS,
						 new SynchronousQueue<Runnable>());
}

newCachedThreadPool :使用SynchronousQueue作为阻塞队列,队列无界,线程的空闲时限为60秒。这种类型的线程池非常适用IO密集的服务,因为IO请求具有密集、数量巨大、不持续、服务器端CPU等待IO响应时间长的特点。服务器端为了能提高CPU的使用率就应该为每个IO请求都创建一个线程,以免CPU因为等待IO响应而空闲。

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

newFixedThreadPool:需指定核心线程数,核心线程数和最大线程数相同,使用LinkedBlockingQueue 作为阻塞队列,队列无界,线程空闲时间0秒。这种类型的线程池可以适用CPU密集的工作,在这种工作中CPU忙于计算而很少空闲,由于CPU能真正并发的执行的线程数是一定的(比如四核八线程),所以对于那些需要CPU进行大量计算的线程,创建的线程数超过CPU能够真正并发执行的线程数就没有太大的意义。

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService (
			new ThreadPoolExecutor(1,1, 0L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() )
	);
}

newSingleThreadExecutor:池中只有一个线程工作阻塞队列无界,它能保证按照任务提交的顺序来执行任务。

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值