<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們經常在需要提升效能或者專案架構解耦的過程中,使用執行緒池非同步執行任務,經常使用ThreadPoolExecutor建立執行緒池。那麼Spring對非同步任務是如何處理的呢?
估計或多或少了解過一些,比如@EnableAsync可以開啟非同步任務,@Async用於註解說明當前方法是非同步執行,下面使用demo看看Spring的非同步任務如何執行。
pom依賴,其實僅依賴Spring core context 就可以了,這裡演示,另外spring boot還要許多好玩的特性。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>2.1.3.RELEASE</version> <scope>test</scope> </dependency> </dependencies>
main & controller
@RestController @SpringBootApplication public class AsyncMain { public static void main(String[] args) { SpringApplication.run(AsyncMain.class, args); } @Autowired private TaskService taskService; @RequestMapping(value = "/async-task", method = RequestMethod.GET) public String asyncMapping(){ System.out.println(Thread.currentThread().getThreadGroup() + "http-------" + Thread.currentThread().getName()); taskService.doTask(); return "exec http ok--------------"; } }
非同步任務服務
@EnableAsync @Service public class TaskService { @Async public String doTask(){ System.out.println(Thread.currentThread().getThreadGroup() + "-------" + Thread.currentThread().getName()); return "do task done"; } }
執行main方法,存取localhost:8080/async-task,控制檯可以看到:
可以看到執行緒的name是task-1,而http存取的執行緒是http-nio-xxx。說明任務非同步執行了。然而Spring的非同步任務是如何執行的呢,我們也並未建立執行緒池,難道Spring替我們建立了?
首先,需要執行非同步任務,必須建立執行緒池,那我們來揪出Spring建立的執行緒池,從啟動紀錄檔可以看出
Spring預設給我們建立了applicationTaskExecutor的ExecutorService的執行緒池。
通過原始碼分析,Spring boot的starter已經給我們設定了預設的執行器
/** * {@link EnableAutoConfiguration Auto-configuration} for {@link TaskExecutor}. * * @author Stephane Nicoll * @author Camille Vienot * @since 2.1.0 */ @ConditionalOnClass(ThreadPoolTaskExecutor.class) @Configuration @EnableConfigurationProperties(TaskExecutionProperties.class) public class TaskExecutionAutoConfiguration { /** * Bean name of the application {@link TaskExecutor}. */ public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor"; private final TaskExecutionProperties properties; private final ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers; private final ObjectProvider<TaskDecorator> taskDecorator; public TaskExecutionAutoConfiguration(TaskExecutionProperties properties, ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers, ObjectProvider<TaskDecorator> taskDecorator) { this.properties = properties; this.taskExecutorCustomizers = taskExecutorCustomizers; this.taskDecorator = taskDecorator; } @Bean @ConditionalOnMissingBean public TaskExecutorBuilder taskExecutorBuilder() { TaskExecutionProperties.Pool pool = this.properties.getPool(); TaskExecutorBuilder builder = new TaskExecutorBuilder(); builder = builder.queueCapacity(pool.getQueueCapacity()); builder = builder.corePoolSize(pool.getCoreSize()); builder = builder.maxPoolSize(pool.getMaxSize()); builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout()); builder = builder.keepAlive(pool.getKeepAlive()); builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix()); builder = builder.customizers(this.taskExecutorCustomizers); builder = builder.taskDecorator(this.taskDecorator.getIfUnique()); return builder; } @Lazy @Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME, AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME }) @ConditionalOnMissingBean(Executor.class) public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) { return builder.build(); } }
追根溯源:在Spring boot的autoconfigure中已經定義了預設實現
Spring為我們定義了兩種實現,如上圖所示,根據Spring boot的設定定律,我們可以通過設定來定義非同步任務的引數
@ConfigurationProperties("spring.task.execution") public class TaskExecutionProperties { private final Pool pool = new Pool(); /** * Prefix to use for the names of newly created threads. */ private String threadNamePrefix = "task-"; public Pool getPool() { return this.pool; } public String getThreadNamePrefix() { return this.threadNamePrefix; } public void setThreadNamePrefix(String threadNamePrefix) { this.threadNamePrefix = threadNamePrefix; } public static class Pool { /** * Queue capacity. An unbounded capacity does not increase the pool and therefore * ignores the "max-size" property. */ private int queueCapacity = Integer.MAX_VALUE; /** * Core number of threads. */ private int coreSize = 8; /** * Maximum allowed number of threads. If tasks are filling up the queue, the pool * can expand up to that size to accommodate the load. Ignored if the queue is * unbounded. */ private int maxSize = Integer.MAX_VALUE; /** * Whether core threads are allowed to time out. This enables dynamic growing and * shrinking of the pool. */ private boolean allowCoreThreadTimeout = true; /** * Time limit for which threads may remain idle before being terminated. */ private Duration keepAlive = Duration.ofSeconds(60);
省略get set方法,spring boot的設定以spring.task.execution開頭,引數的設定參考如上原始碼的屬性設定。
各位可以自行嘗試,當然因為Spring bean的定義方式,我們可以複寫bean來達到自定義的目的
@Lazy @Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME, AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME }) @ConditionalOnMissingBean(Executor.class) public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) { return builder.build(); }
比如:
@Configuration @EnableAsync public class TaskAsyncConfig { @Bean public Executor initExecutor(){ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //客製化執行緒名稱,還可以客製化執行緒group executor.setThreadFactory(new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread t = new Thread(Thread.currentThread().getThreadGroup(), r, "async-task-" + threadNumber.getAndIncrement(), 0); return t; } }); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setKeepAliveSeconds(5); executor.setQueueCapacity(100); // executor.setRejectedExecutionHandler(null); return executor; } }
重啟,存取localhost:8080/async-task,證明我們寫的Executor已經覆蓋系統預設了。
方法斷點跟蹤
執行非同步任務使用Spring CGLib動態代理AOP實現
可以看出動態代理後使用AsyncExecutionInterceptor來處理非同步邏輯,執行submit方法
同理可以看出,預設的taskExecutor使用BeanFactory中獲取。
預設使用SimpleAsyncUncaughtExceptionHandler處理非同步異常。下面我們來試試
@EnableAsync @Service public class TaskService { @Async public String doTask(){ System.out.println(Thread.currentThread().getThreadGroup() + "-------" + Thread.currentThread().getName()); throw new RuntimeException(" I`m a demo test exception-----------------"); } }
預設會列印logger.error("Unexpected exception occurred invoking async method: " + method, ex);紀錄檔
public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { private static final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class); @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { if (logger.isErrorEnabled()) { logger.error("Unexpected exception occurred invoking async method: " + method, ex); } } }
執行測試
需要實現AsyncConfigurer介面,可以看到Spring要我們配合EnableAsync與Configuration註解同時使用
/** * Interface to be implemented by @{@link org.springframework.context.annotation.Configuration * Configuration} classes annotated with @{@link EnableAsync} that wish to customize the * {@link Executor} instance used when processing async method invocations or the * {@link AsyncUncaughtExceptionHandler} instance used to process exception thrown from * async method with {@code void} return type. * * <p>Consider using {@link AsyncConfigurerSupport} providing default implementations for * both methods if only one element needs to be customized. Furthermore, backward compatibility * of this interface will be insured in case new customization options are introduced * in the future. * * <p>See @{@link EnableAsync} for usage examples. * * @author Chris Beams * @author Stephane Nicoll * @since 3.1 * @see AbstractAsyncConfiguration * @see EnableAsync * @see AsyncConfigurerSupport */ public interface AsyncConfigurer { /** * The {@link Executor} instance to be used when processing async * method invocations. */ @Nullable default Executor getAsyncExecutor() { return null; } /** * The {@link AsyncUncaughtExceptionHandler} instance to be used * when an exception is thrown during an asynchronous method execution * with {@code void} return type. */ @Nullable default AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return null; } }
demo,如下改造
@Configuration @EnableAsync public class TaskAsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //客製化執行緒名稱,還可以客製化執行緒group executor.setThreadFactory(new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { //重新定義一個名稱 Thread t = new Thread(Thread.currentThread().getThreadGroup(), r, "async-task-all" + threadNumber.getAndIncrement(), 0); return t; } }); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setKeepAliveSeconds(5); executor.setQueueCapacity(100); // executor.setRejectedExecutionHandler(null); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new AsyncUncaughtExceptionHandler() { @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { System.out.println("do exception by myself"); } }; } }
記住,此時,Spring就不會替我們管理Executor了,需要我們自己初始化
executor.initialize();
觀其原始碼就是new 一個ThreadPoolExecutor
@Override protected ExecutorService initializeExecutor( ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) { BlockingQueue<Runnable> queue = createQueue(this.queueCapacity); ThreadPoolExecutor executor; if (this.taskDecorator != null) { executor = new ThreadPoolExecutor( this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler) { @Override public void execute(Runnable command) { Runnable decorated = taskDecorator.decorate(command); if (decorated != command) { decoratedTaskMap.put(decorated, command); } super.execute(decorated); } }; } else { executor = new ThreadPoolExecutor( this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler); } if (this.allowCoreThreadTimeOut) { executor.allowCoreThreadTimeOut(true); } this.threadPoolExecutor = executor; return executor; }
執行,結果如下
Spring boot將簡單的ThreadPoolExecutor通過封裝成了非同步任務,大大方便了程式的開發。
然而我們在如上的範例中,並沒有處理程式的非同步執行結果,其實Spring定義了結果的處理
/** * AOP Alliance {@code MethodInterceptor} that processes method invocations * asynchronously, using a given {@link org.springframework.core.task.AsyncTaskExecutor}. * Typically used with the {@link org.springframework.scheduling.annotation.Async} annotation. * * <p>In terms of target method signatures, any parameter types are supported. * However, the return type is constrained to either {@code void} or * {@code java.util.concurrent.Future}. In the latter case, the Future handle * returned from the proxy will be an actual asynchronous Future that can be used * to track the result of the asynchronous method execution. However, since the * target method needs to implement the same signature, it will have to return * a temporary Future handle that just passes the return value through * (like Spring's {@link org.springframework.scheduling.annotation.AsyncResult} * or EJB 3.1's {@code javax.ejb.AsyncResult}). * * <p>When the return type is {@code java.util.concurrent.Future}, any exception thrown * during the execution can be accessed and managed by the caller. With {@code void} * return type however, such exceptions cannot be transmitted back. In that case an * {@link AsyncUncaughtExceptionHandler} can be registered to process such exceptions. * * <p>As of Spring 3.1.2 the {@code AnnotationAsyncExecutionInterceptor} subclass is * preferred for use due to its support for executor qualification in conjunction with * Spring's {@code @Async} annotation. * * @author Juergen Hoeller * @author Chris Beams * @author Stephane Nicoll * @since 3.0 * @see org.springframework.scheduling.annotation.Async * @see org.springframework.scheduling.annotation.AsyncAnnotationAdvisor * @see org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor */ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered {
<p>In terms of target method signatures, any parameter types are supported. * However, the return type is constrained to either {@code void} or * {@code java.util.concurrent.Future}. In the latter case, the Future handle * returned from the proxy will be an actual asynchronous Future that can be used * to track the result of the asynchronous method execution. However, since the * target method needs to implement the same signature, it will have to return * a temporary Future handle that just passes the return value through * (like Spring's {@link org.springframework.scheduling.annotation.AsyncResult} * or EJB 3.1's {@code javax.ejb.AsyncResult}).
如果程式不返回void或者Future,那麼通過AsyncResult來返回一個結果
另外Spring還定義了一個Task,即定時任務task,原理相同。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45