JavaEE开发之Spring中的多线程编程以及任务定时器详解

上篇博客我们详细的聊了Spring中的事件的发送和监听,也就是常说的广播或者通知一类的东西,详情请移步于《JavaEE开发之Spring中的事件发送与监听以及使用@Profile进行环境切换》。本篇博客我们就聊一下Spring中的并发编程,看一下Spring中的多线程编程和任务的定时执行。下方我们就来聊一下这两方面的内容。

 

一、Spring中的多线程

本部分就来看一下Spring框架封装下的多线程编程。因为毕竟是被Spring封装过的异步并发编程,所以用起来还是蛮简单的。主要还是ThreadPoolTaskExecutor的使用。下方会给出Spring框架中多线程编程的小示例。

 

1、创建异步执行任务的Service

下方的AsyncTaskService类就是我们创建的可支持异步任务执行的Service。主要使用了@Async注解来声明方法,使其支持异步任务的执行。在AsyncTaskService方法中,将当前线程的ID进行了打印,以便于我们进行观察。具体如下所示:

  

 

2、配置类中的异步设置

我们需要在Spring的配置类中进行异步的相关配置,然后我们使用@Async注解的方法才支持异步执行。下方就是相关的异步配置,首先使用@EnableAsync注解开启异步任务支持,然后实现AsyncConfigurer接口即可。下方TaskExecutorConfig配置类中的两个方法就是AsyncConfigurer接口的方法。

在该接口中,getAsyncExecutor()方法负责提供ThreadPoolTaskExecutor类的对象,在该方法中,我们实例化了ThreadPoolTaskExecutor类对象,然后对其进行了相应的配置。corePoolSize的值说明可开启核心线程数,稍后会进行演示。而maxPoolSize的值是可开启的最大线程数,queueCapacity的属性表示每个线程中可容纳的任务数。

而下方的getAsyncUncaughtExceptionHandler()方法是负责提供异常错误的句柄的。我们可以创建一个继承自ErrorHandler类的错误处理句柄类,在这个类中重写handleUncaughtException()方法,之后在该方法中返回该错误处理句柄的对象即可。当有异常时,会执行我们创建的这个错误句柄中相应的handleUncaughtException()方法。下方我们没有给出错误处理的句柄,直接就返回null即可。

  

 

我们可以看一下ThreadPoolTaskExecutor类中的属性,下方是这些属性的默认值了。

  

 

 

3.创建测试的Main方法

下方就是我们创建用来测试的Main方法,其中从Spring容器中获取AsyncTaskService的对象,然后在For循环中调用该对象的异步方法即可。具体代码如下所示:

  

 

4.运行结果

我们分别给ThreadPoolTaskExecutor对象的corePoolSizequeueCapacity属性设置相应的值,然后看起运行结果。

(1)  corePoolSize = 10 && queueCapacity = 10(并行队列的异步执行)

下方是我们将开启线程数和线程队列容量都设置为10的运行结果。从下方我们可以看出,开启了10个线程,然后进行的异步执行。类似于iOS开发中GCD的并行队列的异步执行方式。

  

 

(2)  corePoolSize = 1 && queueCapacity = 10(串行队列的异步执行)

接着我们将开启线程的最大值设置为1,然后将每个线程队列的容量设置为10。下方是其运行输出结果,我们可以看出只开启了一个新的线程来顺序执行这for循环中的10次任务都会在这个线程队列中排队执行。这也就是串行队列的异步执行了。

  

 

 

二、任务定时器

接下来我们就来看一下Spring框架中是如何使用@Schedule注解来实现任务定时执行的。@Scheduled注解中,有一些参数,我们可以为这些参数提供不同值来指定不同类型的Schedule。在@Scheduled任务定时器中,我们常用的属性有fixedRatefixedDelay, cron这三个属性。下方我们将分别讨论着三个属性的具体用法,特别是cron属性,功能是比较强大的。废话少说,进入本部分的主题。

 

1、开启Schedule支持

首先我们得在Java配置类中开启Schedule的支持,也就是在配置类中添加上@EnableScheduling注解。具体如下所示。配置完后,我们就可以在我们的Service类中使用@Schedule注解来创建定时任务了。

  

 

2、创建定时执行的任务测试方法

接下来,我们就来创建Service类中的定时任务执行的测试方法。dateFormat属性负责日期的格式化,sleepTimes数组中的数字则代表每次执行任务所休眠的时间,用来模拟每次任务执行所需要的时间。使用index来标记当前执行的任务次数。

每调用一次testCase()方法,任务就执行一次。testCase()方法中的代码比较简单,在此就不做过多赘述了。

  

 

3、fixRate =  3000

fixRate =  3000表示两个相邻任务的开始执行时间的间隔必须大于等于3000毫秒。下方代码片段,是将fixeRate()方法使用@Scheduled声明为定时任务。在fixedRate()方法中调用了this.testCase()方法。在@Scheduled注解中,我们为fixedRate属性指定了一个值为3000ms, 也就是3秒的时间。下方我们会根据运行结果,来看一下fixedRate = 3000的具体作用。

  

 

下方截图,就是上述代码运行的测试结果,也就是fixedRate = 3000时的运行结果。然后我们也根据这个结果画出啊了一个任务执行的时间轴。 第一个任务执行开始到结束使用了1秒钟的时间,因为我们设定任务执行的固定频率是3秒,所以下次任务要经过两秒后才能执行。也就是说fixedRate = 3000,意味着从上一个任务执行开始,到下一个任务开始执行的间隔必须大于等于3秒,如果上一个任务的执行时间大于等于3秒的话,那么该任务执行完毕后,就紧接着执行下个任务

  

 

4、fixedDelay = 3000

下方代码段是将fixedDelay属性设置成3000ms,表示在上次任务执行完成之后间隔3秒后在执行下一次任务。

  

下方就是上述代码所输出的结果,从下方结果中我们不难看出,上个任务结束的时间与下个任务开始的间隔为3秒。具体结果如下所示:

  

 

 5、cron="0/3 * * * * ?"

cron属性后边紧跟着的是一个表达式,该表达式可表示特定的时间以及某些时间段,当系统时间到达我们设定的时间或者时间段后就会执行我们所指定的任务。在下方代码片段中,我们将cron的值设置为"0/3 * * * * ?"。该表达式的第一个参数就代表着秒,后边的参数表示任意。0/3表示从秒开始每3秒执行一次。

  

 

下方就是上述代码的运行结果,从下方结果中我们可以看出,从上一个任务的结束,到下一个任务的开始并不是中间隔着3秒的时间。而是本次任务结束后,如果下次任务开始执行的时间是3秒的倍数,那么下个任务就开始执行。如果不是,就继续等待。所以,我们不难看出下方任务开始的时间都是3的倍数。

  

 

6、cron的参数表达式

上一小节只是给出了cron参数的一种形式,接下来我们将详细的看一下cron的参数表达式的构建规则。下方是cron表达式每个位置所表示的时间值,以及取值范围。

  

  • 秒:表达式的第一位是秒,允许值是0-59,"1,3,5"表示第1、3、5秒执行一次任务“12-15”相当于12,13,14,15,表示这个范围内的秒数每秒执行一次。“*”就是同配符了,表示任意秒数。“3/5”表示从第三秒开始,每5秒执行一次。(, - * /)
  • 分钟:分钟可支持的表达式形式与秒数一致,可以是“0-59”,“23,45,59”,“3/8”,“*”等格式。(, - * /)
  • 小时:与分钟和秒的差不多,其允许的范围是0-23,可以使用“, - * /”。(, - * /)
  • 日期:日期的范围是1-31,可以表示为“1-5”,“1,4,5”, “*”。“?”表示无意义的值,类似于“*”号。“2/3”,“L”也就是Last的缩写表示该月的最后一天。“W”就是Work的缩写,用法为“18W”表示离18号最近的工作日,比如18号是周日,那么里18号最近的工作日就是下周一了,那也就是19号。如果18号是周六,那么离18号最近的工作日就是本周的周五,也就是17号。如果18号就是周一~周五的某一天,因为18号当天就是工作日,所以“18W”就表示18号就是18号。(? , - * / L W C)
  • 月份:月份的范围是在1~12个月,其中可以使用(, - * /)
  • 星期:星期的范围是1-7,1表示周日,7表示周六。可以使用(? , - * / L  C #)。#号只能在星期中使用,2#3则表示当月第三个个星期的星期一。
  • 年(可选): 范围为1970-2099,可以使用(, - * /)

看完上面,我们可以给出一个综合的实例,比如“2,6-8    3/5    * LW 3 ?    *”则表示离每年的3月份最后一天最近的工作日中每小时的从第三分钟开始每隔5分钟的第2秒以及6~8秒执行一次任务。其实这种表达信息的方式就类似于正则表达式,也就是火星文。cron的用法还是比较灵活的,而且是比较强大的。

 

本篇博客就先到这儿吧,下篇博客我们会继续聊Spring的相关内容。

github源码分享地址:https://github.com/lizelu/SpringDemo

 

posted @ 2017-04-05 09:36  青玉伏案  阅读(6903)  评论(3编辑  收藏  举报