springboot多线程
ThreadPoolTaskExecutor就是在java中ThreadPoolExecutor的基础上封装的
最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]
1、ThreadPoolTaskExecutor
package com.hcp.tools.dxc; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; @Configuration @EnableAsync public class ThreadExecutorConfig { private final Logger logger = LoggerFactory.getLogger(this.getClass()); /** 核心线程数 */ private int corePoolSize = 10; /** 最大线程数 */ private int maxPoolSize = 10; /** 队列数 */ private int queueCapacity = 10; @Bean public Executor threadPoolTaskExecutor(){ logger.debug("创建线程池"); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //设置核心线程数 executor.setCorePoolSize(corePoolSize); //设置最大线程数 executor.setMaxPoolSize(maxPoolSize); //设置队列数 executor.setQueueCapacity(queueCapacity); //设置线程名称前缀 executor.setThreadNamePrefix("threadPoolTaskExecutor-》》》"); // AbortPolicy: 直接抛出RejectedExecutionException异常 // CallerRunsPolicy: 交由主线程执行 // DiscardOldestPolicy: 抛弃工作队列中旧的任务,将新任务添加进队列;会导致被丢弃的任务无法再次被执行 // DiscardPolicy: 抛弃当前任务;会导致被抛弃的任务无法再次被执行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); // 执行初始化 executor.initialize(); return executor; } }
package com.hcp.tools.dxc; import com.hcp.tools.Tool; import org.springframework.scheduling.annotation.Async; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Async("threadPoolTaskExecutor") public class TestThread { @GetMapping("a") public void run(){ System.out.println(Thread.currentThread().getName()); } }
/** * @Async标注的方法,称之为异步方法;这些方法将在执行的时候, * 将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。 */
2、ThreadPoolExecutor
package com.hcp.tools.dxc; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.*; @Configuration @EnableAsync public class ThreadExecutorConfig { @Bean public Executor myThreadPool() { // 设置核心线程数 int corePoolSize = 5; // 设置最大线程数 int maxPoolSize = 5; // 设置工作队列大小 int queueCapacity = 2000; // 最大存活时间 long keepAliveTime = 30; // 设置线程名称前缀 String threadNamePrefix = "myThreadPool-->"; // 设置自定义拒绝策略.当工作队列已满,线程数为最大线程数的时候,接收新任务抛出RejectedExecutionException异常 RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { throw new RejectedExecutionException("自定义的RejectedExecutionHandler"); } }; // 自定义线程工厂 ThreadFactory threadFactory = new ThreadFactory() { private int i = 1; @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName(threadNamePrefix + i); i++; return thread; } }; // 初始化线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>(queueCapacity), threadFactory, rejectedExecutionHandler); return threadPoolExecutor; } }
package com.hcp.tools.dxc; import com.hcp.tools.Tool; import org.springframework.scheduling.annotation.Async; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Async("myThreadPool") public class TestThread { @GetMapping("a") public void run(){ System.out.println(Thread.currentThread().getName()); } }
3、springboot默认配置的线程池(ThreadPoolTaskExecutor)
package com.hcp.tools.dxc; import com.hcp.tools.Tool; import org.springframework.scheduling.annotation.Async; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Async() public class TestThread { @GetMapping("a") public void run(){ System.out.println(Thread.currentThread().getName()); } }
线程池配置
一、ThreadPoolExecutor的重要参数
1、corePoolSize:核心线程数
* 核心线程会一直存活,及时没有任务需要执行
* 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
* 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
2、maxPoolSize:最大线程数
* 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
* 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
3、 keepAliveTime:线程空闲时间
* 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
* 如果allowCoreThreadTimeout=true,则会直到线程数量=0
4、allowCoreThreadTimeout:允许核心线程超时
5、queueCapacity:任务队列容量(阻塞队列)
* 当核心线程数达到最大时,新任务会放在队列中排队等待执行
6、rejectedExecutionHandler:任务拒绝处理器
* 两种情况会拒绝处理任务:
- 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
* 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
* ThreadPoolExecutor类有几个内部实现类来处理这类情况:
- AbortPolicy 丢弃任务,抛运行时异常
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
* 实现RejectedExecutionHandler接口,可自定义处理器
二、ThreadPoolExecutor执行顺序
线程池按以下行为执行任务
1. 当线程数小于核心线程数时,创建线程。
2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3. 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,抛出异常,拒绝任务
三、如何设置参数
计算公式分为:
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),即taskstasktime个线程数。假设系统每秒任务数为100 -1000,每个任务耗时0.1秒,则需要1000.1至1000*0.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:还没有测试过)
1
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数量,否则很容易就会执行拒绝策略;
2.ArrayBlockingQueue
使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。
3.LinkedBlockingQueue
使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。
4.PriorityBlockingQueue
PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。