1. 程式人生 > >springboot使用@Scheduled做定時任務,以及連線池問題

springboot使用@Scheduled做定時任務,以及連線池問題

本人覺得@Scheduled使用起來太方便了,大大減少了程式碼量(2月份剛來公司的時候,還單純以為只用java的Timer來寫呢),突然覺得springboot特別便利....。好了,不多說,開始寫@Scheduled部落格了。

這裡就說一些定時任務的簡單實用。首先需要在啟動類中加上@EnableScheduling註解來開啟定時任務。

@SpringBootApplication
@EnableTransactionManagement
@EnableScheduling
public class SmartSchoolApplication {

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

配置完後就可以使用定時任務了,例子如下:

@Component
public class TestSchedule {
    
    @Scheduled(fixedDelay = 1000 ) //每一秒執行一次
    public void printDate(){
        System.out.println(new Date().toLocaleString());
    }
}

作為程式設計師,只會這個肯定是不夠的,上面的定時任務時每秒鐘執行一次邏輯程式碼,但是我想每天的上午八點執行一次,此定時任務就不合適了,除非我們在邏輯程式碼中做時間判斷,判斷當前時間是不是早上的8點。這樣做固然可以,但是這樣做的話是不是有點low。我們需要改進的話,就需要說說@Scheduled三種定時任務的執行方式。

@Scheduled有三種定時任務的執行方式,包括fixedDelay、fixedRate、corn表示式,下面就分別講講這三種執行方式的不同。

 

fixedDelay:指定兩次任務執行的時間間隔(毫秒),此時間間隔指的是,前一次任務結束與下一個任務開始的間隔。如:@Scheduled(fixedDelay = 5*1000 ),表示第一個任務結束後,過5秒後,開始第二個任務。

 

fixedRate:指定兩次任務執行的時間間隔(毫秒),此時間間隔指的是,前一個任務開始與下一個任務開始的間隔。如:@Scheduled(fixedRate= 5*1000 ),表示第一個任務開始後(第一個任務執行時間小於5秒),第一個任務開始後的第6秒,開始第二個任務。如果第一個任務執行時間大於5秒,第一個任務結束後,直接開始第二個任務。

fixedDelay與fixedRate差別可以檢視圖例區別:https://blog.csdn.net/applebomb/article/details/52400154

 

cron使用表達是進行任務的執行,例如:@Scheduled(cron = "0/15 * * * * ? ")每隔15秒執行一次

cron一般是六個或七個欄位,分別是:

1. Seconds (秒) 
2. Minutes (分) 
3. Hours (時) 
4. Day (每月的第幾天,day-of-month) 
5. Month (月) 
6. Day (每週的第幾天,day-of-week) 
7. Year (年 可選欄位)

每隔欄位的範圍以及特殊字元

秒 :範圍:0-59 
分 :範圍:0-59
時 :範圍:0-23
天(月) :範圍:1-31,但要注意一些特別的月份2月份沒有隻能1-28,有些月份沒有31
月 :用0-11 或用字串 “JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC” 表示
天(周):用1-7表示(1 = 星期日)或用字元口串“SUN, MON, TUE, WED, THU, FRI and SAT”表示
年:範圍:1970-2099

“/”:表示為“每”,如“0/10”表示每隔10分鐘執行一次,“0”表示為從“0”分開始, “3/20”表示表示每隔20分鐘執行一次
“?”:只用於月與周,表示不指定值
“L”:只用於月與周,5L用在月表示為每月的最後第五天天;1L用在周表示每週的最後一天;
“W”::表示有效工作日(週一到週五),只能出現在day-of-month,系統將在離指定日期的最近的有效工作日觸發事件。例如:在 DayofMonth使用5W,如果5日是星期六,則將在最近的工作日:星期五,即4日觸發。如果5日是星期天,則在6日(週一)觸發;如果5日在星期一到星期五中的一天,則就在5日觸發。另外一點,W的最近尋找不會跨過月份 
“#”:用於確定每個月第幾個星期幾,只能出現在DayofMonth域。例如在4#2,表示某月的第二個星期三。
“*” 代表整個時間段。

注意:每個元素可以是一個值(如6),一個連續區間(9-12),一個間隔時間(8-18/4)(/表示每隔4小時),一個列表(1,3,5),萬用字元。由於"月份中的日期"和"星期中的日期"這兩個元素互斥的,必須要對其中一個設定‘?’

表示式例項(網上搜的,感覺例子都一樣, = =):

0 0 10,14,16 * * ? 每天上午10點,下午2點,4點
0 0/30 9-17 * * ?   朝九晚五工作時間內每半小時
0 0 12 ? * WED 表示每個星期三中午12點 
"0 0 12 * * ?" 每天中午12點 
"0 15 10 ? * *" 每天上午10:15 
"0 15 10 * * ?" 每天上午10:15 
"0 15 10 * * ? *" 每天上午10:15 
"0 15 10 * * ? 2005" 2005年的每天上午10:15 
"0 * 14 * * ?" 在每天下午2點到下午2:59期間的每1分鐘 
"0 0/5 14 * * ?" 在每天下午2點到下午2:55期間的每5分鐘 
"0 0/5 14,18 * * ?" 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘 
"0 0-5 14 * * ?" 在每天下午2點到下午2:05期間的每1分鐘 
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44 
"0 15 10 ? * MON-FRI" 週一至週五的上午10:15 
"0 15 10 15 * ?" 每月15日上午10:15 
"0 15 10 L * ?" 每月最後一日的上午10:15 
"0 15 10 ? * 6L" 每月的最後一個星期五上午10:15 
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最後一個星期五上午10:15 
"0 15 10 ? * 6#3" 每月的第三個星期五上午10:15 

 

-------------------------------------------------分隔符---------------------------------------------------

 

上面的所有基本上滿足大部分的定時任務。但是,你有沒有想過,如果寫多個@Scheduled,你會發現他們是使用的同一個執行緒,意不意外,驚不驚喜。在我自己的測試中,所有的定時任務確實是使用的一個執行緒,測試例子如下:

@Component
public class TestSchedule {
    Logger logger = LoggerFactory.getLogger(TestSchedule.class);

    @Scheduled(fixedDelay = 1000)  //定時任務1
    public void printXXXXXXX(){
        try{
            Thread.sleep(5000);  //睡眠5秒
            logger.info(Thread.currentThread().getName()); //列印當前執行緒名字

        }catch (Exception e){
            logger.error(e.getMessage());
        }
    }

    @Scheduled(fixedDelay = 1000)  //定時任務2
    public void printYYYYYYY(){
        try{
            Thread.sleep(5000);
            logger.info(Thread.currentThread().getName());

        }catch (Exception e){
            logger.error(e.getMessage());
        }
    }
}

執行上面的程式碼,檢視日誌。

執行後你會發現,他們使用的是同一個執行緒,而且你會發現printXXXXXXX原本每6秒(定時任務1000+sleep 5000)執行一次的,現在變成了每10秒執行一次,原因就是受到了printYYYYYYY的Thread.sleep(5000)的影響。這對注重時間範圍的定時任務影響特別大,此時就需要定時任務的執行緒池的配置。

@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }

    @Bean(destroyMethod="shutdown")
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(10); //指定執行緒池大小
    }
}

上面的配置只是增加了執行緒池大小,可以根據專案的定時任務多少進行配置。這樣的話,定時任務之間就不會相互影響,新增這個配置後,繼續執行測試程式碼,日誌如下: