栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【多线程】@Async注解和线程池

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【多线程】@Async注解和线程池

@Async注解和线程池
  • @Async
    • 1.@Async是什么
    • 2.@Async的使用
      • 步骤一:在启动类上面加@EnableAsync注解
      • 步骤二:在对应的方法上加@Async注解
    • 3.@Async的使用原理
      • 问题1:当不指定的时候,默认线程池是什么?具体配置是什么?看源码
      • 问题2:为什么被@Async注解的方法只能返回 void 或者 Future 类型(如AsyncResult类),如果返回了其他类型则为null
    • 4.课后作业:什么情况下会默认使用SimpleAsyncTaskExecutor线程池?

@Async 1.@Async是什么

Spring自带了支持任务调度和异步方法调用的注解,在需要被调用的方法上加上@Async注解,则该方法会被异步调用

2.@Async的使用 步骤一:在启动类上面加@EnableAsync注解
@EnableAsync
@SpringBootApplication
public class OperationApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(OperationApplication.class, args);
    }
}

或者
创建一个配置类,在配置类上面加@EnableAsync注解

@Configuration
@EnableAsync
public class ThreadPoolConfig {

}

注意:@Async默认启动的线程池,属性

步骤二:在对应的方法上加@Async注解
    @Async
    public void method() {
        log.info("执行该线程的名字为:" + Thread.currentThread().getName());
    }
3.@Async的使用原理
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface Async {

	
	String value() default "";

}

剖析注释可以发现

@Async 注解中有一个 value 属性,看注释应该是可以指定是哪个线程池的

写一个demo

相关的依赖

    
        
            org.springframework.boot
            spring-boot-starter
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.projectlombok
            lombok
        
         
            org.springframework.boot
            spring-boot-starter-test
            test
        
    
    

启动类

@EnableAsync // 开启异步注解
@SpringBootApplication
public class ThreadDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ThreadDemoApplication.class, args);
    }
}

调用的方法

@Slf4j
@Service
public class AsyncService {

    @Async
    public void method() {
        log.info("执行该线程的名字为:" + Thread.currentThread().getName());
    }
}

写一个调用异步方法的接口

@Slf4j
@RestController
public class TestController {

    @Resource
    private AsyncService asyncService;

    @GetMapping("/test")
    public void test() {
    	log.info("执行test方法的线程的名字为:" + Thread.currentThread().getName());
        asyncService.method();
    }
}

调用结果

INFO 22864 --- [         task-1] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:task-1

为了一探默认线程池的配置,就行黑盒压测一下

@Slf4j
@RestController
public class TestController {

    @Resource
    private AsyncService asyncService;

    @GetMapping("/test")
    public void test(@RequestParam("num") Integer num) {
        log.info("执行test方法的线程的名字为:" + Thread.currentThread().getName());
        for (int i = 0; i < num; i++) {
            asyncService.method();
        }
    }
}

不多废话,直接进行100000次异步调用

http://localhost:8080/test?num=100000

发现控制台并未报错,单堆内存几乎要爆炸,怀疑可能是一个无界队列,任务有可能都进队列里面排队了,导致内存飙升


默认的线程池有导致内存溢出的风险。

可以通过创建自定义线程池代替默认线程池

@Configuration
@EnableAsync
public class ThreadPoolConfig {

    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(50);
        taskExecutor.setQueueCapacity(200);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setThreadNamePrefix("Thread-");
        taskExecutor.setAwaitTerminationSeconds(60);
        return taskExecutor;
    }
}

控制台

2021-12-24 18:18:38.014  INFO 10688 --- [      Thread-30] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:Thread-30
2021-12-24 18:18:38.014  INFO 10688 --- [       Thread-2] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:Thread-2
2021-12-24 18:18:38.014  INFO 10688 --- [       Thread-8] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:Thread-8
2021-12-24 18:18:38.014  INFO 10688 --- [      Thread-15] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:Thread-15
2021-12-24 18:18:38.014  INFO 10688 --- [      Thread-25] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:Thread-25
2021-12-24 18:18:38.014  INFO 10688 --- [       Thread-5] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:Thread-5
2021-12-24 18:18:38.014  INFO 10688 --- [      Thread-31] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:Thread-31
2021-12-24 18:18:38.014  INFO 10688 --- [      Thread-24] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:Thread-24
2021-12-24 18:18:38.014  INFO 10688 --- [      Thread-22] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:Thread-22
2021-12-24 18:18:38.014  INFO 10688 --- [       Thread-9] c.e.threaddemo.service.AsyncService      : 执行该线程的名字为:Thread-9
2021-12-24 18:18:38.022 ERROR 10688 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@44a62e23[Running, pool size = 50, active threads = 50, queued tasks = 91, completed tasks = 1001]] did not accept task: org.springframework.aop.interceptor.AsyncExecutionInterceptor$$Lambda$580/2138858786@265a267e] with root cause

java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@293459fe rejected from java.util.concurrent.ThreadPoolExecutor@44a62e23[Running, pool size = 50, active threads = 50, queued tasks = 129, completed tasks = 961]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) ~[na:1.8.0_162]
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) ~[na:1.8.0_162]
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) ~[na:1.8.0_162]
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) ~[na:1.8.0_162]

说明 @Async使用了我们自定义的线程池,当超出最大线程数且队列满的时候,通过默认拒绝策略new ThreadPoolExecutor.AbortPolicy()丢弃任务并抛出RejectedExecutionException异常

问题1:当不指定的时候,默认线程池是什么?具体配置是什么?看源码

value属性被使用的地方就一个,走


F8断点往下走,进入Aspect

其中defaultExecutor就是我们需要知道的默认线程池

private SingletonSupplier defaultExecutor;

SingletonSupplier是一个函数式编程,不懂可以百度,方便了解一下源码的运行

在这里成功获取到了线程池,让我们看看线程池的参数

得到@Async使用的线程池Bean名称为applicationTaskExecutor,显而易见这个队列设置为Integer.MAX_VALUE,所以在上面会导致堆内存溢出

线程池applicationTaskExecutor的属性都是根据TaskExecutionProperties中的Pool来的,以下筛选了部分TaskExecutionProperties的源码:

@ConfigurationProperties("spring.task.execution")
public class TaskExecutionProperties {

	private final Pool pool = new Pool();

	
	private String threadNamePrefix = "task-";

	public Pool getPool() {
		return this.pool;
	}

	public static class Pool {

		
		private int queueCapacity = Integer.MAX_VALUE;

		
		private int coreSize = 8;

		
		private int maxSize = Integer.MAX_VALUE;

		
		private boolean allowCoreThreadTimeout = true;

		
		private Duration keepAlive = Duration.ofSeconds(60);

	}
}
问题2:为什么被@Async注解的方法只能返回 void 或者 Future 类型(如AsyncResult类),如果返回了其他类型则为null
  • 现象

  • 剖析源码

查看异步任务拦截器AsyncExecutionInterceptor的invoke方法

public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered {

	@Override
	@Nullable
	public Object invoke(final MethodInvocation invocation) throws Throwable {
		Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
		Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
		final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
		
		// 1.从map里面拿到当前method对应的线程池对象
		AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
		if (executor == null) {
			throw new IllegalStateException(
					"No executor specified and no default executor set on AsyncExecutionInterceptor either");
		}
		
		// 2.获取执行的对象
		Callable task = () -> {
			try {
				Object result = invocation.proceed();
				if (result instanceof Future) {
					return ((Future) result).get();
				}
			}
			catch (ExecutionException ex) {
				handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
			}
			catch (Throwable ex) {
				handleError(ex, userDeclaredMethod, invocation.getArguments());
			}
			return null;
		};
		
		// 3.执行任务
		return doSubmit(task, executor, invocation.getMethod().getReturnType());
	}
}
 

doSubmit方法判断当returnType不是Future类型的时候会执行summit方法后直接返回一个null

4.课后作业:什么情况下会默认使用SimpleAsyncTaskExecutor线程池?
转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号