- Spring框架提供了線程池和定時(shí)任務(wù)執(zhí)行的抽象接口:TaskExecutor和TaskScheduler來(lái)支持異步執(zhí)行任務(wù)和定時(shí)執(zhí)行任務(wù)功能疤苹。
- 同時(shí)使用框架自己定義的抽象接口來(lái)屏蔽掉底層JDK版本間以及Java EE中的線程池和定時(shí)任務(wù)處理的差異。 為啥懂缕?因?yàn)镾pring想要替你管理線程池的初始化、注解使用、停止等工作。
- 另外Spring還支持集成JDK內(nèi)部的定時(shí)器Timer和Quartz Scheduler框架燎字。
- 這里我們單說(shuō)TaskExecutor和TaskScheduler
ThreadPoolTaskExecutor
配置
<!-- spring thread pool executor -->
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 線程池維護(hù)線程的最少數(shù)量 -->
<property name="corePoolSize" value="5" />
<!-- 允許的空閑時(shí)間 -->
<property name="keepAliveSeconds" value="200" />
<!-- 線程池維護(hù)線程的最大數(shù)量 -->
<property name="maxPoolSize" value="10" />
<!-- 緩存隊(duì)列 -->
<property name="queueCapacity" value="20" />
<!-- 對(duì)拒絕task的處理策略 -->
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
</property>
</bean>
execute(Runable)方法執(zhí)行過程 (與JDK的線程池一樣機(jī)制)
- 如果此時(shí)線程池中的數(shù)量小于corePoolSize,即使線程池中的線程都處于空閑狀態(tài)阿宅,也要?jiǎng)?chuàng)建新的線程來(lái)處理被添加的任務(wù)候衍。
如果此時(shí)線程池中的數(shù)量等于 corePoolSize,但是緩沖隊(duì)列 workQueue未滿洒放,那么任務(wù)被放入緩沖隊(duì)列蛉鹿。
如果此時(shí)線程池中的數(shù)量大于corePoolSize,緩沖隊(duì)列workQueue滿往湿,并且線程池中的數(shù)量小于maxPoolSize妖异,建新的線程來(lái)處理被添加的任務(wù)。
如果此時(shí)線程池中的數(shù)量大于corePoolSize领追,緩沖隊(duì)列workQueue滿他膳,并且線程池中的數(shù)量等于maxPoolSize,那么通過handler所指定的策略來(lái)處理此任務(wù)绒窑。也就是:處理任務(wù)的優(yōu)先級(jí)為:核心線程corePoolSize棕孙、任務(wù)隊(duì)列workQueue、最大線程 maximumPoolSize些膨,如果三者都滿了蟀俊,使用handler處理被拒絕的任務(wù)。
當(dāng)線程池中的線程數(shù)量大于corePoolSize時(shí)订雾,如果某線程空閑時(shí)間超過keepAliveTime肢预,線程將被終止。這樣葬燎,線程池可以動(dòng)態(tài)的調(diào)整池中的線程數(shù)误甚。
測(cè)試使用
@Component
public class TTaskThreadPool {
@Autowired
private TaskExecutor taskExecutor;
public void testGo(){
for (int i = 0; i < 100; i++) {
final int num = i;
this.taskExecutor.execute(()->{
int idMe = num;
System.out.println("before idMe = " + idMe);
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after idMe = " + idMe);
});
}
}
}
TaskExecutor types
There are a number of pre-built implementations of TaskExecutor included with the Spring distribution. In all likelihood, you shouldn’t ever need to implement your own.
SimpleAsyncTaskExecutor
This implementation does not reuse any threads, rather it starts up a new thread for each invocation. However, it does support a concurrency limit which will block any invocations that are over the limit until a slot has been freed up. If you are looking for true pooling, see the discussions of SimpleThreadPoolTaskExecutor and ThreadPoolTaskExecutor below.SyncTaskExecutor
This implementation doesn’t execute invocations asynchronously. Instead, each invocation takes place in the calling thread. It is primarily used in situations where multi-threading isn’t necessary such as simple test cases.ConcurrentTaskExecutor
This implementation is an adapter for a java.util.concurrent.Executor object. There is an alternative, ThreadPoolTaskExecutor, that exposes the Executor configuration parameters as bean properties. It is rare to need to use the ConcurrentTaskExecutor, but if the ThreadPoolTaskExecutor isn’t flexible enough for your needs, the ConcurrentTaskExecutor is an alternative.SimpleThreadPoolTaskExecutor
This implementation is actually a subclass of Quartz’s SimpleThreadPool which listens to Spring’s lifecycle callbacks. This is typically used when you have a thread pool that may need to be shared by both Quartz and non-Quartz components.ThreadPoolTaskExecutor
This implementation is the most commonly used one. It exposes bean properties for configuring a java.util.concurrent.ThreadPoolExecutor and wraps it in a TaskExecutor. If you need to adapt to a different kind of java.util.concurrent.Executor, it is recommended that you use a ConcurrentTaskExecutor instead.WorkManagerTaskExecutor
TaskScheduler
In addition to the TaskExecutor abstraction, Spring 3.0 introduces a TaskScheduler with a variety of methods for scheduling tasks to run at some point in the future.
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Date startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}
Spring also features integration classes for supporting scheduling with the Timer
, part of the JDK since 1.3, and the Quartz Scheduler ( http://quartz-scheduler.org). Both of those schedulers are set up using a FactoryBean
with optional references toTimer
or Trigger
instances, respectively. Furthermore, a convenience class for both the Quartz Scheduler and the Timer
is available that allows you to invoke a method of an existing target object
首先給出幾個(gè)結(jié)論:
- 調(diào)度器本質(zhì)上還是通過juc的ScheduledExecutorService進(jìn)行的
- 調(diào)度器啟動(dòng)后你無(wú)法通過修改系統(tǒng)時(shí)間達(dá)到讓它馬上執(zhí)行的效果
- 被@Schedule注解的方法如果有任何Throwable出現(xiàn), 不會(huì)中斷后續(xù)Task, 只會(huì)打印Error日志
配置
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
使用
在 Spring 管理的 Bean 中的方法上使用 Scheduled 注解。
@Scheduled(fixedDelay = 1000 * 60)
public void checkAndUpdateRegisterZset() {
//Do something
}
Trigger interface
The basic idea of the Trigger is that execution times may be determined based on past execution outcomes or even arbitrary conditions. If these determinations do take into account the outcome of the preceding execution, that information is available within a TriggerContext.
因?yàn)槎〞r(shí)任務(wù)需要記錄任務(wù)的前后執(zhí)行時(shí)間點(diǎn)序列谱净,如果后續(xù)修改了系統(tǒng)時(shí)間窑邦,則會(huì)破壞既定時(shí)間序列,故需要記錄最開始預(yù)訂好的時(shí)間序列壕探。
public interface Trigger {
// 獲取下一個(gè)執(zhí)行時(shí)間
Date nextExecutionTime(TriggerContext triggerContext);
}
// 相對(duì)時(shí)間記錄載體
public interface TriggerContext {
Date lastScheduledExecutionTime();
Date lastActualExecutionTime();
Date lastCompletionTime();
}
Trigger implementations
如常用的一個(gè)表達(dá)式實(shí)現(xiàn):
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
Annotation Support for Scheduling and Asynchronous Execution
Enable scheduling annotations
To enable support for @Scheduled and @Async annotations add @EnableScheduling and @EnableAsync to one of your @Configuration classes:
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
You are free to pick and choose the relevant annotations for your application. For example, if you only need support for @Scheduled, simply omit @EnableAsync. For more fine-grained control you can additionally implement the SchedulingConfigurer and/or AsyncConfigurer interfaces. See the javadocs for full details.
If you prefer XML configuration use the <task:annotation-driven> element.
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
Notice with the above XML that an executor reference is provided for handling those tasks that correspond to methods with the @Async annotation, and the scheduler reference is provided for managing those methods annotated with @Scheduled.
@Scheduled annotation
The @Scheduled annotation can be added to a method along with trigger metadata. For example, the following method would be invoked every 5 seconds with a fixed delay, meaning that the period will be measured from the completion time of each preceding invocation.
@Scheduled(fixedDelay=5000)
public void doSomething() {
// something that should execute periodically
}
@Scheduled(fixedRate=5000)
public void doSomething() {
// something that should execute periodically
}
@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
// something that should execute periodically
}
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should execute on weekdays only
}
Notice that the methods to be scheduled must have void returns and must not expect any arguments. If the method needs to interact with other objects from the Application Context, then those would typically have been provided through dependency injection.
As of Spring Framework 4.3, @Scheduled methods are supported on beans of any scope.
Make sure that you are not initializing multiple instances of the same @Scheduled annotation class at runtime, unless you do want to schedule callbacks to each such instance. Related to this, make sure that you do not use @Configurable on bean classes which are annotated with @Scheduled and registered as regular Spring beans with the container: You would get double initialization otherwise, once through the container and once through the @Configurable aspect, with the consequence of each @Scheduled method being invoked twice.
@Async annotation
The @Async annotation can be provided on a method so that invocation of that method will occur asynchronously. In other words, the caller will return immediately upon invocation and the actual execution of the method will occur in a task that has been submitted to a Spring TaskExecutor. In the simplest case, the annotation may be applied to a void-returning method.
@Async
void doSomething() {
// this will be executed asynchronously
}
Unlike the methods annotated with the @Scheduled annotation,
these methods can expect arguments, because they will be invoked in
the "normal" way by callers at runtime rather than from a scheduled task
being managed by the container. For example, the following is a legitimate
application of the @Async annotation.
@Async
void doSomething(String s) {
// this will be executed asynchronously
}
Even methods that return a value can be invoked asynchronously.
However, such methods are required to have a Future typed return value.
This still provides the benefit of asynchronous execution so that the caller
can perform other tasks prior to calling get() on that Future.
@Async
Future<String> returnSomething(int i) {
// this will be executed asynchronously
}
@Async methods may not only declare a regular java.util.concurrent.Future return type but also Spring’s org.springframework.util.concurrent.ListenableFuture or, as of Spring 4.2, JDK 8’s java.util.concurrent.CompletableFuture: for richer interaction with the asynchronous task and for immediate composition with further processing steps.
@Async can not be used in conjunction with lifecycle callbacks such as @PostConstruct. To asynchronously initialize Spring beans you currently have to use a separate initializing Spring bean that invokes the @Async annotated method on the target then.
public class SampleBeanImpl implements SampleBean {
@Async
void doSomething() {
// ...
}
}
public class SampleBeanInitializer {
private final SampleBean bean;
public SampleBeanInitializer(SampleBean bean) {
this.bean = bean;
}
@PostConstruct
public void initialize
bean.doSomething();
}
}
The task namespace
Beginning with Spring 3.0, there is an XML namespace for configuring TaskExecutor
and TaskScheduler
instances. It also provides a convenient way to configure tasks to be scheduled with a trigger.
The 'scheduler' element
The following element will create a ThreadPoolTaskScheduler instance with the specified thread pool size.
<task:scheduler id="scheduler" pool-size="10"/>
The value provided for the 'id' attribute will be used as the prefix for thread names within the pool. The 'scheduler' element is relatively straightforward.If you do not provide a 'pool-size' attribute, the default thread pool will only have a single thread.
There are no other configuration options for the scheduler.
The 'executor' element
<task:executor
id="executorWithCallerRunsPolicy"
pool-size="5-25"
queue-capacity="100"
keep-alive="120"
rejection-policy="CALLER_RUNS"/>
'scheduled-tasks' element
Basically a "ref" attribute can point to any Spring-managed object, and the "method" attribute provides the name of a method to be invoked on that object. Here is a simple example.
<!--定時(shí)任務(wù)-->
<bean id="scheduleA" class="world.zw.test.taskSpring.TScheduleA">
<constructor-arg value="AAA"/>
</bean>
<bean id="scheduleB" class="world.zw.test.taskSpring.TScheduleA">
<constructor-arg value="BBB"/>
</bean>
<bean id="scheduleC" class="world.zw.test.taskSpring.TScheduleA">
<constructor-arg value="CCC"/>
</bean>
<task:scheduler id="myScheduler" pool-size="10"/>
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="scheduleA" method="fun" fixed-delay="5000" initial-delay="1000"/>
<task:scheduled ref="scheduleB" method="fun" fixed-rate="5000"/>
<task:scheduled ref="scheduleC" method="fun" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>
或者都通過注解
@Configuration
@EnableScheduling
public class TScheduleB {
private String name = "BBB";
@Scheduled(cron="*/5 * * * * *")
public void fun(){
System.out.println("before " + name);
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after idMe = " + name);
}
}
Using the Quartz Scheduler
Quartz uses Trigger
, Job
and JobDetail
objects to realize scheduling of all kinds of jobs. For the basic concepts behind Quartz, have a look at http://quartz-scheduler.org. For convenience purposes, Spring offers a couple of classes that simplify the usage of Quartz within Spring-based applications.
Using the JobDetailFactoryBean
Quartz JobDetail
objects contain all information needed to run a job. Spring provides a JobDetailFactoryBean
which provides bean-style properties for XML configuration purposes. Let’s have a look at an example:
Cron
想了解Cron最好的方法是看Quartz的官方文檔冈钦。本節(jié)也會(huì)大致介紹一下。
Cron表達(dá)式由6~7項(xiàng)組成李请,中間用空格分開瞧筛。從左到右依次是:秒、分导盅、時(shí)较幌、日、月白翻、周幾乍炉、年(可省略)。值可以是數(shù)字滤馍,也可以是以下符號(hào):
*
:所有值都匹配
?
:無(wú)所謂岛琼,不關(guān)心,通常放在“周幾”里
,
:或者
/
:增量值
-
:區(qū)間
下面舉幾個(gè)例子巢株,看了就知道了:
0 * * * * *
:每分鐘(當(dāng)秒為0的時(shí)候)
0 0 * * * *
:每小時(shí)(當(dāng)秒和分都為0的時(shí)候)
*/10 * * * * *
:每10秒
0 5/15 * * * *
:每小時(shí)的5分槐瑞、20分、35分阁苞、50分
0 0 9,13 * * *
:每天的9點(diǎn)和13點(diǎn)
0 0 8-10 * * *
:每天的8點(diǎn)困檩、9點(diǎn)、10點(diǎn)
0 0/30 8-10 * * *
:每天的8點(diǎn)那槽、8點(diǎn)半窗看、9點(diǎn)、9點(diǎn)半倦炒、10點(diǎn)
0 0 9-17 * * MON-FRI
:每周一到周五的9點(diǎn)显沈、10點(diǎn)…直到17點(diǎn)(含)
0 0 0 25 12 ?
:每年12約25日圣誕節(jié)的0點(diǎn)0分0秒(午夜)
0 30 10 * * ? 2016
:2016年每天的10點(diǎn)半
其中的?
在用法上其實(shí)和*
是相同的。但是*
語(yǔ)義上表示全匹配逢唤,而?
并不代表全匹配拉讯,而是不關(guān)心。比如對(duì)于0 0 0 5 8 ? 2016
來(lái)說(shuō)鳖藕,2016年8月5日是周五魔慷,?
表示我不關(guān)心它是周幾。而0 0 0 5 8 * 2016
中的*
表示周一也行著恩,周二也行……語(yǔ)義上和2016年8月5日沖突了院尔,你說(shuō)誰(shuí)優(yōu)先生效呢蜻展。
Ref:
https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#scheduling