ThreadPoolTaskExecutor线程池

ThreadPoolTaskExecutor

背景

需求: 项目二期,需要对某一张表的字段进行扩展,为了兼容二期业务代码逻辑,现需要将一起数据,进行统一计算补充该字段,满足二期逻辑

数据量:50w

解决方案

二期上线初,使用lts -JOB定时任务,统一批量处理老数据,代码实现思想:考虑到,数据量比较大,同时原有系统中已经存在比较多的抛弃任务,故此次处理数据任务,为批量查询数据库,使用单独线程池配合有边界队列ArrayBlockingQueue提高处理数据的效率

ThreadPoolTaskExecutor 参数

ThreadPoolTaskExecutor通常通过XML方式配置,或者通过Executors的工厂方法进行配置。
XML方式配置代码如下:

	<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <!-- 线程池维护线程的最少数量 -->
        <property name="corePoolSize" value="5" />
        <!-- 允许的空闲时间 -->
        <property name="keepAliveSeconds" value="200" />
        <!-- 线程池维护线程的最大数量 -->
        <property name="maxPoolSize" value="20" />
        <!-- 缓存队列 -->
        <property name="queueCapacity" value="20" />
        <!-- 对拒绝task的处理策略 -->
        <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
        </property>
    </bean>

注意:rejectedExecutionHandler字段用于配置拒绝策略,常用的拒绝策略如下:

  • AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
  • CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务
  • DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute
  • DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务

自定义策略,只需要实现RejectedExecutionHandler接口即可

执行任务两种方式

  • 无返回值的任务使用execute(Runnable)
  • 有返回值的任务使用submit(Runnable)

任务处理流程

简单总结一句话:

提交任务,线程池中的线程数可以增长至corePoolSize,之后继续提交任务将暂存至队列中,如果队列满,则看是否能继续增长线程数至maximumPoolSize,超出后将进行拒绝策略处理。显然,如果采用×××队列,那么maximumPoolSize将失效,线程池中的线程最多就是corePoolSize个线程工作。

image-20220317180903586

关闭线程池

调用shutdown或者shutdownNow,两者都不会接受新的任务,而且通过调用要停止线程的interrupt方法来中断线程,有可能线程永远不会被中断,不同之处在于shutdownNow会首先将线程池的状态设置为STOP,然后尝试停止所有线程(有可能导致部分任务没有执行完)然后返回未执行任务的列表。而shutdown则只是将线程池的状态设置为shutdown,然后中断所有没有执行任务的线程,并将剩余的任务执行完

一般少有使用

监控线程池状态

常用状态:

  • taskCount:线程需要执行的任务个数。

  • completedTaskCount:线程池在运行过程中已完成的任务数。

  • largestPoolSize:线程池曾经创建过的最大线程数量。

  • getPoolSize:获取当前线程池的线程数量。

  • getActiveCount:获取活动的线程的数量

    通过继承线程池,重写beforeExecuteafterExecuteterminated方法来在线程执行任务前,线程执行任务结束,和线程终结前获取线程的运行情况,根据具体情况调整线程池的线程数量。

代码实现

xml文件

 <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <!-- 线程池维护线程的最少数量 -->
        <property name="corePoolSize" value="5" />
        <!-- 允许的空闲时间 -->
        <property name="keepAliveSeconds" value="200" />
        <!-- 线程池维护线程的最大数量 -->
        <property name="maxPoolSize" value="20" />
        <!-- 缓存队列 -->
        <property name="queueCapacity" value="20" />
        <!-- 对拒绝task的处理策略 -->
        <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
        </property>
    </bean>

配置类

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class JobRunnerConfig {

   // @Value("${test.statistical.SavedSum.corePoolSize}")
    private int corePoolSize = 5;
	
    //参数可在配置中心配置
    //@Value("${test.statistical.SavedSum.maxPoolSize}")
    private int maxPoolSize = 10;

    /**
     *  使用线程池 statisticalSavedSumForJob
     * @return TaskExecutor
     */
    @Bean
    public ThreadPoolTaskExecutor statisticalSavedSumTaskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setKeepAliveSeconds(600);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setQueueCapacity(0);
        executor.setThreadNamePrefix("测试线程池-");//线程前缀
        return executor;
    }

}

业务实现类

@Service("testService")
public class testServiceImpl implements testService {

	//注入线程池 注意通过名称查找
    @Resource
    private ThreadPoolTaskExecutor statisticalSavedSumTaskExecutor;

    //@Value("${test.statistical.SavedSum.pageSize}")
    private int pageSize = 100;

    //@Value("${test.statistical.SavedSum.threadNum}")
    private int threadNum = 5;

    //可配置在配置中心 /配置文件中 @Value("${test.statistical.SavedSum.queueCapacity}")
    private static int queueCapacity = 1000;

    private static BlockingQueue<String> queue = new ArrayBlockingQueue<>(queueCapacity);


    @Override
    public void statisticalSavedSum() {
		
        try {
			//分页查询所有的数据 这里直接创建一个集合不做查询了
			List<String> testList = new ArrayList<>()
			
            if (testList.size() == 0 || testList.isEmpty()) {
                return;
            }
            for (String str : testList) {
				//遍历查询出的数据放到队列中
                queue.offer(str, 5, TimeUnit.SECONDS);
            }
			//统计执行失败的任务
            List<Future<Integer>> failList = new ArrayList<>();
            for (int i = 0; i < threadNum; i++) {
                Future<Integer> failCount = statisticalSavedSumTaskExecutor
                        .submit(new TestStatisticalSavedSumThread(memberQueue)); //TestStatisticalSavedSumThread真正执行的类
                failList.add(failCount);
            }
            int count;
            count = failList.stream().mapToInt(this::getCount).sum();
            logger.warn("统计失败数量:{}", count);
        } catch (InterruptedException e) {
            logger.error("发生中断异常:", e);
        }
    }


    private int getCount(Future<Integer> fail) {
        int n = 0;
        try {
            n = fail.get();
        } catch (InterruptedException e) {
            logger.error("发生中断异常:", e);
        } catch (ExecutionException e) {
            logger.error(" 线程返回结果异常:", e);
        }
        return n;
    }
}

Thread执行类

public class TestStatisticalSavedSumThread implements Callable<Integer>  {

	//队列
    private BlockingQueue<String> memberQueue;

	//有参构造 
    public MemberStatisticalSavedSumThread(BlockingQueue<String> memberQueue) {
        this.memberQueue = memberQueue;
    }

    @Override
    public Integer Call() throws Exception {
        int failCount = 0;
        try {
			//无限循环获取队列中的数据处理
            while (memberQueue.size() > 0) {
				// ArrayBlockingQueue 如果队列满了,或者是空的,那么都会在执行操作的时候,阻塞住
                // 指定时间内获取不到,退出监听
                String str = memberQueue.poll(5, TimeUnit.SECONDS);
                if (Optional.ofNullable(str).isPresent()) {
                   //再次执行真正的业务方法
				   
                }
            }
        } catch (Exception e) {
            Thread.currentThread().interrupt();
        }
        return failCount;
    }
}

批量多线程高效处理任务

posted @ 2022-03-17 19:05  Mr*宇晨  阅读(303)  评论(0编辑  收藏  举报