杂记之@Asyns异步调用的正确打开姿势

环境:

spring;spring boot

摘要说明:

项目开发过程中往往会出现需异步调用的情况,以便提高系统的响应速度或者提高部分业务的处理时间;

但异步调用需要根据系统的承受能力做好相关配置,而不是放任随意使用;

如系统批跑5000条数据做相关业务处理;若循环异步处理,则很可能将数据库给压垮;

所以这里需要控制系统异步线程池的大小及线程池的满额执行策略;

步骤:

1.@Asyns的使用

@Asyns的使用其实很简单,在系统已允许异步调用的情况下,在bean管理的类的方法上加上此注解就表示该方法可;


import java.util.Date;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class TestAsyncComponent {

	@Async
	// @Async("taskScheduler")
	public void test() {
		try {
			System.out.println("测试异步!!!" + new Date());
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}

其中@Async表示使用默认线程池;指定线程池需注明线程池id;

 2.传统spring项目(如ssm)下配置线程池

传统的spring项目下配置开启异步调用只需一个spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.0.xsd
	http://www.springframework.org/schema/task
	http://www.springframework.org/schema/task/spring-task-4.0.xsd
	">
<!-- 开启异步调用,缺省的executor给@Async使用 -->
<task:annotation-driven />
</beans>

指定自定义线程池如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.0.xsd
	http://www.springframework.org/schema/task
	http://www.springframework.org/schema/task/spring-task-4.0.xsd
	">
	<!-- 给@Async指定线程池 -->
	<task:annotation-driven executor="asyncExecutor" />
	<!-- 线程池中最小线程数为20,最大数为150, 队列的capacity数为10,任务完成后,线程池中保留最小线程数20,超出的在10s内未使用将被结束掉,当使用的线程数超出线程池最大线程数且队列也已满时,由调用者所在线程来执行相应任务 -->
	<task:executor id="asyncExecutor" pool-size="20-150"
		queue-capacity="10" keep-alive="10" rejection-policy="CALLER_RUNS" />
	
</beans>

其中task中executor的相关配置如下:

  • id:线程池id,要求唯一,可与@Async搭配使用
  • pool-size:core size(最小线程数,缺省为1)-max size(最大线程池,缺省为Integer.MAX_VALUE)
  • queue-capacity:当最小的线程数已经被占用满后,新的任务会被放进queue里面,当这个queue的capacity也被占满之后,pool里面会创建新线程处理这个任务,直到总线程数达到了max size。缺省值为:Integer.MAX_VALUE
  • keep-alive:当系统创建的线程数多于core size时,若keep-alive(秒)没用调用则收回多于线程
  • rejection-policy:当pool已经达到max size的时候,如何处理新任务:
ABORT(缺省):抛出TaskRejectedException异常,然后不执行 

DISCARD:不执行,也不抛出异常 

DISCARD_OLDEST:丢弃queue中最旧的那个任务 

CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行即同步调用

实际线程池创建和销毁线程如下 

  • 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
  • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maxPoolSize,建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maxPoolSize,那么通过handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
  • 当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

:建议使用上述的CALLER_RUNS满额策略,此种策略就可以很好的解决摘要中的批量循环执行业务;当线程池满的时候自己线程同步执行,当线程池有空闲立刻又是异步执行

 3.spring boot下配置线程池

spring boot下开启使用@Async可参考之前写过的一篇文章:https://blog.youkuaiyun.com/u010904188/article/details/82893473

这里主要说下spring boot下线程池的配置:

// 启动异步调用
    @EnableAsync
    @Configuration
    class TaskPoolConfig {
        // 核心线程数(setCorePoolSize)10:线程池创建时候初始化的线程数
        // 最大线程数(setMaxPoolSize)20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        // 缓冲队列(setQueueCapacity)200:用来缓冲执行任务的队列
        // 允许线程的空闲时间(setKeepAliveSeconds)60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        // 线程池名的前缀(setThreadNamePrefix):设置好了之后可以方便我们定位处理任务所在的线程池
        // 线程池对拒绝任务的处理策略(setRejectedExecutionHandler):这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute
        // 方法的调用线程中运行被拒绝的任务(setWaitForTasksToCompleteOnShutdown);如果执行程序已关闭,则会丢弃该任务
        // setWaitForTasksToCompleteOnShutdown(true)该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于异步线程池的销毁。
        // 同时,这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
        @Bean("taskExecutor")
        public Executor taskExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(20);
            executor.setQueueCapacity(200);
            executor.setKeepAliveSeconds(60);
            executor.setThreadNamePrefix("taskExecutor-");
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            executor.setWaitForTasksToCompleteOnShutdown(true);
            executor.setAwaitTerminationSeconds(60);
            return executor;
        }
    }

在这里我们可以看到基本配置都是类似的;就多了个线程销毁机制和线程等待时间设置;

官方api地址如下:https://docs.spring.io/spring-framework/docs/5.1.4.RELEASE/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值