线程池ThreadPoolTaskExecutor配置说明
线程池ThreadPoolTaskExecutor使用详解
ThreadPoolTaskExecutor通常通过XML方式配置,或者通过Executors
的工厂方法进行配置。
XML方式配置代码如下:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="8"/> <!--核心线程数 --> <property name="maxPoolSize" value="16"/> <!--最大线程数 --> <property name="keepAliveSeconds" value ="3000"/> <!--线程最大空闲时间 --> <property name="queueCapacity" value="200"/> <!-- 队列大小 --> <property name="threadNamePrefix" value="TASK_EXECUTOR"/> <property name="rejectedExecutionHandler"> <bean class="java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy"/> </property> </bean>
rejectedExecutionHandler字段用于配置拒绝策略,常用的拒绝策略如下:
AbortPolicy,用于被拒绝任务的处理程序,它将抛出RejectedExecutionException。
CallerRunsPolicy,用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
DiscardOldestPolicy,用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
DiscardPolicy,用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
其他说明:
为了实现某些特殊的业务需求,用户可以选择使用自定义策略,只需实现RejectedExecutionHandler接口即可。
建议配置threadNamePrefix属性,出问题时可以更方便的进行排查。
提交任务
无返回值的任务使用execute(Runnable)
有返回值的任务使用submit(Runnable)
处理流程
当一个任务被提交到线程池时,首先查看线程池的核心线程是否都在执行任务,否就选择一条线程执行任务,是就执行第二步。
查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第三步。
查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第四步。
查看线程池是否已满,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。
在ThreadPoolExecutor中表现为:
如果当前运行的线程数小于corePoolSize,那么就创建线程来执行任务(执行时需要获取全局锁)。
如果运行的线程大于或等于corePoolSize,那么就把task加入BlockQueue。
如果创建的线程数量大于BlockQueue的最大容量,那么创建新线程来执行该任务。
如果创建线程导致当前运行的线程数超过maximumPoolSize,就根据饱和策略来拒绝该任务。
关闭线程池
调用shutdown或者shutdownNow,两者都不会接受新的任务,而且通过调用要停止线程的interrupt方法来中断线程,有可能线程永远不会被中断,不同之处在于shutdownNow会首先将线程池的状态设置为STOP,然后尝试停止所有线程(有可能导致部分任务没有执行完)然后返回未执行任务的列表。而shutdown则只是将线程池的状态设置为shutdown,然后中断所有没有执行任务的线程,并将剩余的任务执行完。
配置线程个数
如果是CPU密集型任务,那么线程池的线程个数应该尽量少一些,一般为CPU的个数+1条线程。
如果是IO密集型任务,那么线程池的线程可以放的很大,如2*CPU的个数。
对于混合型任务,如果可以拆分的话,通过拆分成CPU密集型和IO密集型两种来提高执行效率;如果不能拆分的的话就可以根据实际情况来调整线程池中线程的个数。
监控线程池状态
常用状态:
taskCount:线程需要执行的任务个数。
completedTaskCount:线程池在运行过程中已完成的任务数。
largestPoolSize:线程池曾经创建过的最大线程数量。
getPoolSize获取当前线程池的线程数量。
getActiveCount:获取活动的线程的数量
通过继承线程池,重写beforeExecute,afterExecute和terminated方法来在线程执行任务前,线程执行任务结束,和线程终结前获取线程的运行情况,根据具体情况调整线程池的线程数量。
____________________________________________________________________________________________________________________________________________________________
ThreadPoolTaskExecutor 线程池简单创建
https://www.jianshu.com/p/f2a7aff2e4c8
1.创建
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);//核心线程大小
executor.setMaxPoolSize(10);//最大线程大小
executor.setQueueCapacity(100);//队列最大容量
executor.setKeepAliveSeconds(3000);//存活时间
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//拒绝执行时如何处理
2. 使用
executor.submit(new ThreadDemo());//或者executor.execute(new ThreadDemo());
// ----------------------------
public class ThreadDemo implements Runnable {
@Override
public void run() {
//业务处理
}
}
使用到这里已经结束,下面是一些相关说明
3. 执行过程
3.1 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
3.2 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
3.3 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maxPoolSize,建新的线程来处理被添加的任务。
4.4 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maxPoolSize,那么通过handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
4. 拒绝处理
4.1 ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
4.2 ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
4.3 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面。
4.4 ThreadPoolExecutor.CallerRunsPolicy:由调用者处理该任务 。
5. submit和execute区别
- 接收的参数不一样
- submit有返回值,execute没有返回值
- submit异常处理(future.get())
https://www.cnblogs.com/qq376324789/p/9729535.html
springboot中使用
@Configuration @EnableAsync public class Test02 { @Bean("taskModuleExecutor") ThreadPoolTaskExecutor getCrawler1(){ ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); threadPoolTaskExecutor.setCorePoolSize(3); threadPoolTaskExecutor.setMaxPoolSize(10); threadPoolTaskExecutor.setQueueCapacity(200); threadPoolTaskExecutor.setThreadNamePrefix("task-concurrent-work"); threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); threadPoolTaskExecutor.initialize(); return threadPoolTaskExecutor; } }
@Resource(name="taskModuleExecutor") private TaskExecutor taskExecutor;
一般实际开发中经常用到多线程,所以需要使用线程池了,
ThreadPoolTaskExecutor通常通过XML方式配置,或者通过Executors
的工厂方法进行配置。
XML方式配置代码如下:交给spring来管理;
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <!-- 核心线程数 --> <property name="corePoolSize" value="4000" /> <!-- 最大线程数 --> <property name="maxPoolSize" value="20000" /> <!-- 队列最大长度 --> <property name="queueCapacity" value="2000" /> <!-- 线程池维护线程所允许的空闲时间 --> <property name="keepAliveSeconds" value="30" /> <!-- 线程池对拒绝任务(无线程可用)的处理策略 --> <property name="rejectedExecutionHandler"> <bean class="java.util.concurrent.ThreadPoolExecutor$DiscardPolicy" /> </property> </bean>
rejectedExecutionHandler
字段用于配置拒绝策略,常用的拒绝策略如下:
AbortPolicy,用于被拒绝任务的处理程序,它将抛出RejectedExecutionException。
CallerRunsPolicy,用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
DiscardOldestPolicy,用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute
。
DiscardPolicy,用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
提交任务
无返回值的任务使用execute(Runnable)
有返回值的任务使用submit(Runnable)
案例代码
threadPoolTaskExecutor.execute(new Runnable() { public void run() { synchronized (Controller01.class) { try { HttpUtils.get("http://192.168.31.223:8085/test2.do"); System.out.println(System.currentTimeMillis()); } catch (Exception e) { e.printStackTrace(); } } } });
任务处理流程:
- 当一个任务被提交到线程池时,首先查看线程池的核心线程是否都在执行任务,否就选择一条线程执行任务,是就执行第二步。
- 查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第三步。
- 查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第四步。
- 查看线程池是否已满,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。
在ThreadPoolExecutor中表现为:
- 如果当前运行的线程数小于corePoolSize,那么就创建线程来执行任务(执行时需要获取全局锁)。
- 如果运行的线程大于或等于corePoolSize,那么就把task加入BlockQueue。
- 如果创建的线程数量大于BlockQueue的最大容量,那么创建新线程来执行该任务。
- 如果创建线程导致当前运行的线程数超过maximumPoolSize,就根据饱和策略来拒绝该任务。
关闭线程池
调用shutdown或者shutdownNow,
两者都不会接受新的任务,而且通过调用要停止线程的interrupt方法来中断线程,有可能线程永远不会被中断,
不同之处在于shutdownNow会首先将线程池的状态设置为STOP,然后尝试停止所有线程(有可能导致部分任务没有执行完)然后返回未执行任务的列表。
而shutdown则只是将线程池的状态设置为shutdown,然后中断所有没有执行
任务的线程,并将剩余的任务执行完。
常用状态:
- taskCount:线程需要执行的任务个数。
- completedTaskCount:线程池在运行过程中已完成的任务数。
- largestPoolSize:线程池曾经创建过的最大线程数量。
- getPoolSize获取当前线程池的线程数量。
- getActiveCount:获取活动的线程的数量
通过继承线程池,重写beforeExecute
,afterExecute
和terminated
方法来在线程执行任务前,线程执行任务结束,和线程终结前获取线程的运行情况,根据具体情况调整线程池的线程数量
使用场景
1、当你的任务是非必要的时候。比如记录操作日志、通知第三方服务非必要信息等,可以使用线程池处理非阻塞任务
2、当你的任务非常耗时时候,可以采用线程池技术
3、当请求并发很高时,可以采用线程池技术优化处理
多线程是不是能加快处理速度?
在使用多线程时,一定要知道一个道理:处理速度的最终决定因素是CPU、内存等,在单CPU(无论多少核)上,分配CPU资源的单位是“进程”而不是“线程”。
我们可以做一个简单的试验:
假设我要拷贝100万条数据,单CPU电脑,用一个进程,在单线程的情况下,CPU占用率为5%,耗时1000秒。那么当在这个进程下,开辟10个线程同时去运行,是不是CPU占用率增加到50%,耗时减少到100秒呢?显然不是。我实测出来的情况是这样的:
“CPU占用率仍然是5%,总耗时仍然是1000秒。且每个线程的运行时间也为1000秒。”
总结:
第一,
看硬件。如果是在比较强大的、多CPU的服务器上运行程序,可以使用多线程来提高并发数和执行速度。
但是线程也不宜过多,即使是16个CPU的服务器,同一时间最多也只能真正意义上地并发处理16个线程,多出来的线程还是要等待。
第二,
看用途。如果你不在乎处理速度,仅仅是为了提高并发处理能力,那么理所当然地用多线程,
但是如果你仅仅是想提高处理速度,且又是在单CPU机器上运行,那么多线程并不值得。
如果你的任务很耗时,且可以一部分、一部分地做,那么最好不要用多线程(好比搬 砖,单线程一次搬10块,总共搬10天,但搬一块算一块,到第9天的时候,你就搬完90块砖了;
如果你用10个线程同时去搬砖,同样要搬10天,但是到第9天的时候,这10个线程100块砖都“还在路上”,一块砖都没搬完!)。
Java ThreadPoolTaskExecutor 线程池的常见问题
https://blog.csdn.net/weixin_43611528/article/details/123083314
重要参数
corePoolSize:核心线程数,常开的线程数,默认值:1。需注意:如果设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭直到关闭为0
queueCapacity:阻塞队列(任务队列),需注意spring的默认值是Integer.MAX_VALUE,必须要手动修改,否则如果任务过多可能会造成服务的OOM
maxPoolSize:最大线程数,默认值是Integer.MAX_VALUE
拒绝策略
具体使用哪种策略需要基于实际业务情况选择
AbortPolicy():该策略是线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
DiscardOldestPolicy():丢弃队列中最老的任务,队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
DiscardPolicy():如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常
CallerRunsPolicy():交由调用方线程运行,比如 main 线程;如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
任务执行和线程创建策略
优先使用核心线程数-->>核心线程数不足-->>放入阻塞队列-->>当阻塞队列满的情况下-->>开启新的线程执行任务,直到线程数=最大线程数-->>当线程数达到最大线程数时-->>基于设置的拒绝策略处理新的任务
核心线程数怎么设置
分IO密集还是CPU密集(不是绝对的,是基于经验设置)
CPU密集设置为跟核心数一样大小
IO密集型设置为2倍CPU核心数(一般服务都是IO密集型)