1. 程式人生 > >spring非同步方法和定時任務

spring非同步方法和定時任務

1.功能說明

Spring提供了Async註解來實現方法的非同步呼叫。
即當呼叫Async標識的方法時,呼叫執行緒不會等待被呼叫方法執行完成即返回繼續執行以下操作,而被呼叫的方法則會啟動一個獨立執行緒來執行此方法。

這種非同步執行的方式通常用於處理介面中不需要返回給使用者的資料處理。比如當註冊的時候,只需要將使用者資訊返回使用者,而關於資訊的儲存操作可以使用非同步執行。

Spring提供了Scheduled註解來實現定時任務的功能。

在非同步方法和定時任務功能中都是開發這自己定義需要執行的方法,然後交給Spring容器管理執行緒,並執行相應的方法。在使用非同步方法和定時任務的時候需要特別注意的是執行緒池的配置以及任務中異常的處理。下面對這兩個功能進行簡單介紹。

2.實現

2.1Async非同步方法的實現

2.1.1Spring配置檔案
定義一個執行緒池,然後引入。執行緒在空閒後會根據存活時間配置進行關閉。
在applicationContext.xml同目錄下建立檔案threadPool.xml內容如下,然後在applicationContext.xml中引入threadPool.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:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
                        
	<!-- 開啟非同步,並引入執行緒池 -->
	<task:annotation-driven executor="threadPool" />
	
	<!-- 定義執行緒池 -->
	<bean id="threadPool"
		class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
		<!-- 核心執行緒數,預設為1 -->
		<property name="corePoolSize" value="5" />
 
		<!-- 最大執行緒數,預設為Integer.MAX_VALUE -->
		<property name="maxPoolSize" value="20" />
 
		<!-- 佇列最大長度,一般需要設定值>=notifyScheduledMainExecutor.maxNum;預設為Integer.MAX_VALUE -->
		<property name="queueCapacity" value="500" />
 
		<!-- 執行緒池維護執行緒所允許的空閒時間,預設為60s -->
		<property name="keepAliveSeconds" value="30" />
 
		<!-- 完成任務自動關閉 , 預設為false-->
		<property name="waitForTasksToCompleteOnShutdown" value="true" />
 
		<!-- 核心執行緒超時退出,預設為false -->
		<property name="allowCoreThreadTimeOut" value="true" />
 
		<!-- 執行緒池對拒絕任務(無執行緒可用)的處理策略,目前只支援AbortPolicy、CallerRunsPolicy;預設為後者 -->
		<property name="rejectedExecutionHandler">
			<!-- AbortPolicy:直接丟擲java.util.concurrent.RejectedExecutionException異常 -->
			<!-- CallerRunsPolicy:主執行緒直接執行該任務,執行完之後嘗試新增下一個任務到執行緒池中,可以有效降低向執行緒池內新增任務的速度 -->
			<!-- DiscardOldestPolicy:拋棄舊的任務、暫不支援;會導致被丟棄的任務無法再次被執行 -->
			<!-- DiscardPolicy:拋棄當前任務、暫不支援;會導致被丟棄的任務無法再次被執行 -->
			<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
		</property>
	</bean>
</beans>


2.1.2SpringBoot則是新增@EnableAsync註解

@EnableAsync
@SpringBootApplication
@ServletComponentScan
@MapperScan("com.cmc.schedule.model.mapper") //配置掃描mapper介面的地址
public class NLPApplication extends SpringBootServletInitializer {
 
	//不使用springboot內嵌tomcat啟動方式
	@Override
	protected SpringApplicationBuilder configure(
			SpringApplicationBuilder application) {
		return application.sources(NLPApplication.class);
	}
 
	public static void main(String[] args) {
		SpringApplication.run(NLPApplication.class, args);
	}
 
	//預設使用fastjson解析
	@Bean
	public HttpMessageConverters fastJsonHttpMessageConverters() {
		FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
		FastJsonConfig fastJsonConfig = new FastJsonConfig();
		fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
		fastConverter.setFastJsonConfig(fastJsonConfig);
		HttpMessageConverter<?> converter = fastConverter;
		return new HttpMessageConverters(converter);
	}
}


2.1.3這一步Spring和SpringBoot都是一樣的,建立一個非同步方法的類

package com.cmc.tst;
 
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
 
/**
 * @Component 註解必須要有,否則無法將此bean注入
 * 當然也可以使用其他的註解,只要可以裝配就行
 * 
 * @author chenmc
 * @date 2017年9月4日 下午3:38:29
 */
@Component
public class MyAsync {
 
	/**
	 * @Async 表明這是一個非同步方法,也就是說當呼叫這個方法時,
	 * spring會建立一條執行緒來執行這個方法。
	 * 注意:不能使用static來修飾此方法,否則@Async無效
	 * 
	 * @author chenmc
	 * @date 2017年9月4日 下午3:34:24
	 */
	@Async
	public void asyncMethod(){
		System.out.println(Thread.currentThread().getName());
	}
}


2.1.4測試呼叫非同步方法,我這裡使用的是JUnit4測試
 

	@Autowired
	MyAsync async;
	
	@Test
	public void test() {
		System.out.println(Thread.currentThread().getName() + "start");
		//MyAsync async = new MyAsync(); //自己new出來的物件@Async將無效,必須要spring注入的
		async.asyncMethod();
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "end");
	}

這裡總結一下@Async註解無效的可能點
一、非同步方法使用static修飾
二、非同步類沒有使用@Component註解(或其他註解)導致spring無法掃描到非同步類
三、測試非同步方法不能與非同步方法在同一個類中
四、測試類中需要使用@Autowired或@Resource等註解自動注入,不能自己手動new物件
五、如果使用SpringBoot框架必須在啟動類中增加@EnableAsync註解

2.2 Scheduled定時任務實現

定時任務標識註解Scheduled,定義如下:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    定時任務
    String cron() default "";

    String zone() default "";
    //上次執行結束到下次執行開始
    long fixedDelay() default -1;
    
    String fixedDelayString() default "";
    上次執行開始到本次執行開始
    long fixedRate() default -1;

    fixedRate或者fixedDelay的時候第一次的延遲時間
    long initialDelay() default -1;

}

 3 對於執行緒池的選擇順序

3.1 Async對於執行緒池的選擇順序

Async執行緒池的選擇順序如下圖所示:

Spring在執行async標識的非同步方法的時候首先會在Spring的上下文中搜索型別為TaskExecutor或者名稱為“taskExecutor”的bean,當可以找到的時候,就將任務提交到此執行緒池中執行。當不存在以上執行緒池的時候,Spring會手動建立一個SimpleAsyncTaskExecutor執行非同步任務。

另外當標識async註解的時候指定了,value值,Spring會使用指定的執行緒池執行。比如以下:
@Async(value = "asyncTaskThreadPool")

這個時候Spring會去上下文中找名字為asyncTaskThreadPool的bean,並執行非同步任務,找不到,會丟擲異常。

3.2 Scheduled對於執行緒池的選擇順序

Scheduled對於執行緒池的選擇順序如下圖所示:

當Spring執行定時任務的時候,首先會在上下文中找型別為TaskScheduler或者名稱為taskScheduler的bean,找不到的時候會手動建立一個執行緒執行此task。