亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解SpringBoot定時(shí)任務(wù)功能

 更新時(shí)間:2022年05月18日 10:21:16   作者:千云  
這篇文章主要介紹了SpringBoot定時(shí)任務(wù)功能詳細(xì)解析,這次的功能開(kāi)發(fā)過(guò)程中也算是對(duì)其內(nèi)涵的進(jìn)一步了解,以后遇到定時(shí)任務(wù)的處理也更清晰,更有效率了,對(duì)SpringBoot定時(shí)任務(wù)相關(guān)知識(shí)感興趣的朋友一起看看吧

一 背景

項(xiàng)目中需要一個(gè)可以動(dòng)態(tài)新增定時(shí)定時(shí)任務(wù)的功能,現(xiàn)在項(xiàng)目中使用的是xxl-job定時(shí)任務(wù)調(diào)度系統(tǒng),但是經(jīng)過(guò)一番對(duì)xxl-job功能的了解,發(fā)現(xiàn)xxl-job對(duì)項(xiàng)目動(dòng)態(tài)新增定時(shí)任務(wù),動(dòng)態(tài)刪除定時(shí)任務(wù)的支持并不是那么好,所以需要自己手動(dòng)實(shí)現(xiàn)一個(gè)定時(shí)任務(wù)的功能

二 動(dòng)態(tài)定時(shí)任務(wù)調(diào)度

1 技術(shù)選擇

Timer or ScheduledExecutorService

這兩個(gè)都能實(shí)現(xiàn)定時(shí)任務(wù)調(diào)度,先看下Timer的定時(shí)任務(wù)調(diào)度

  public class MyTimerTask extends TimerTask {
    private String name;
    public MyTimerTask(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        //task
        Calendar instance = Calendar.getInstance();
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(instance.getTime()));
    }
}
Timer timer = new Timer();
MyTimerTask timerTask = new MyTimerTask("NO.1");
//首次執(zhí)行,在當(dāng)前時(shí)間的1秒以后,之后每隔兩秒鐘執(zhí)行一次
timer.schedule(timerTask,1000L,2000L);

在看下ScheduledThreadPoolExecutor的實(shí)現(xiàn)

//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
    new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
executorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        //do something
    }
},initialDelay,period, TimeUnit.HOURS);

兩個(gè)都能實(shí)現(xiàn)定時(shí)任務(wù),那他們的區(qū)別呢,使用阿里p3c會(huì)給出建議和區(qū)別

多線程并行處理定時(shí)任務(wù)時(shí),Timer運(yùn)行多個(gè)TimeTask時(shí),只要其中之一沒(méi)有捕獲拋出的異常,其它任務(wù)便會(huì)自動(dòng)終止運(yùn)行,使用ScheduledExecutorService則沒(méi)有這個(gè)問(wèn)題。

從建議上來(lái)看,是一定要選擇ScheduledExecutorService了,我們看看源碼看看為什么Timer出現(xiàn)問(wèn)題會(huì)終止執(zhí)行

/**
 * The timer thread.
 */
private final TimerThread thread = new TimerThread(queue);
public Timer() {
    this("Timer-" + serialNumber());
}
public Timer(String name) {
    thread.setName(name);
    thread.start();
}

新建對(duì)象時(shí),我們看到開(kāi)啟了一個(gè)線程,那么這個(gè)線程在做什么呢?一起看看

class TimerThread extends Thread {
  boolean newTasksMayBeScheduled = true;
  /**
   * 每一件一個(gè)任務(wù)都是一個(gè)quene
   */
  private TaskQueue queue;
  TimerThread(TaskQueue queue) {
      this.queue = queue;
  }
  public void run() {
      try {
          mainLoop();
      } finally {
          // Someone killed this Thread, behave as if Timer cancelled
          synchronized(queue) {
              newTasksMayBeScheduled = false;
              queue.clear();  // 清除所有任務(wù)信息
          }
      }
  }
  /**
   * The main timer loop.  (See class comment.)
   */
  private void mainLoop() {
      while (true) {
          try {
              TimerTask task;
              boolean taskFired;
              synchronized(queue) {
                  // Wait for queue to become non-empty
                  while (queue.isEmpty() && newTasksMayBeScheduled)
                      queue.wait();
                  if (queue.isEmpty())
                      break; // Queue is empty and will forever remain; die
                  // Queue nonempty; look at first evt and do the right thing
                  long currentTime, executionTime;
                  task = queue.getMin();
                  synchronized(task.lock) {
                      if (task.state == TimerTask.CANCELLED) {
                          queue.removeMin();
                          continue;  // No action required, poll queue again
                      }
                      currentTime = System.currentTimeMillis();
                      executionTime = task.nextExecutionTime;
                      if (taskFired = (executionTime<=currentTime)) {
                          if (task.period == 0) { // Non-repeating, remove
                              queue.removeMin();
                              task.state = TimerTask.EXECUTED;
                          } else { // Repeating task, reschedule
                              queue.rescheduleMin(
                                task.period<0 ? currentTime   - task.period
                                              : executionTime + task.period);
                          }
                      }
                  }
                  if (!taskFired) // Task hasn't yet fired; wait
                      queue.wait(executionTime - currentTime);
              }
              if (taskFired)  // Task fired; run it, holding no locks
                  task.run();
          } catch(InterruptedException e) {
          }
      }
  }
}

我們看到,執(zhí)行了 mainLoop(),里面是 while (true)方法無(wú)限循環(huán),獲取程序中任務(wù)對(duì)象中的時(shí)間和當(dāng)前時(shí)間比對(duì),相同就執(zhí)行,但是一旦報(bào)錯(cuò),就會(huì)進(jìn)入finally中清除掉所有任務(wù)信息。

這時(shí)候我們已經(jīng)找到了答案,timer是在被實(shí)例化后,啟動(dòng)一個(gè)線程,不間斷的循環(huán)匹配,來(lái)執(zhí)行任務(wù),他是單線程的,一旦報(bào)錯(cuò),線程就終止了,所以不會(huì)執(zhí)行后續(xù)的任務(wù),而ScheduledThreadPoolExecutor是多線程執(zhí)行的,就算其中有一個(gè)任務(wù)報(bào)錯(cuò)了,并不影響其他線程的執(zhí)行。

2 使用ScheduledThreadPoolExecutor

從上面看,使用ScheduledThreadPoolExecutor還是比較簡(jiǎn)單的,但是我們要實(shí)現(xiàn)的更優(yōu)雅一些,所以選擇 TaskScheduler來(lái)實(shí)現(xiàn)

@Component
public class CronTaskRegistrar implements DisposableBean {
    private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);
    @Autowired
    private TaskScheduler taskScheduler;
    public TaskScheduler getScheduler() {
        return this.taskScheduler;
    }
    public void addCronTask(Runnable task, String cronExpression) {
        addCronTask(new CronTask(task, cronExpression));
    }
    private void addCronTask(CronTask cronTask) {
        if (cronTask != null) {
            Runnable task = cronTask.getRunnable();
            if (this.scheduledTasks.containsKey(task)) {
                removeCronTask(task);
            }
            this.scheduledTasks.put(task, scheduleCronTask(cronTask));
        }
    }
    public void removeCronTask(Runnable task) {
        Set<Runnable> runnables = this.scheduledTasks.keySet();
        Iterator it1 = runnables.iterator();
        while (it1.hasNext()) {
            SchedulingRunnable schedulingRunnable = (SchedulingRunnable) it1.next();
            Long taskId = schedulingRunnable.getTaskId();
            SchedulingRunnable cancelRunnable = (SchedulingRunnable) task;
            if (taskId.equals(cancelRunnable.getTaskId())) {
                ScheduledTask scheduledTask = this.scheduledTasks.remove(schedulingRunnable);
                if (scheduledTask != null){
                    scheduledTask.cancel();
                }
            }
        }
    }
    public ScheduledTask scheduleCronTask(CronTask cronTask) {
        ScheduledTask scheduledTask = new ScheduledTask();
        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
        return scheduledTask;
    }
    @Override
    public void destroy() throws Exception {
        for (ScheduledTask task : this.scheduledTasks.values()) {
            task.cancel();
        }
        this.scheduledTasks.clear();
    }
}

TaskScheduler是本次功能實(shí)現(xiàn)的核心類,但是他是一個(gè)接口

public interface TaskScheduler {
   /**
    * Schedule the given {@link Runnable}, invoking it whenever the trigger
    * indicates a next execution time.
    * <p>Execution will end once the scheduler shuts down or the returned
    * {@link ScheduledFuture} gets cancelled.
    * @param task the Runnable to execute whenever the trigger fires
    * @param trigger an implementation of the {@link Trigger} interface,
    * e.g. a {@link org.springframework.scheduling.support.CronTrigger} object
    * wrapping a cron expression
    * @return a {@link ScheduledFuture} representing pending completion of the task,
    * or {@code null} if the given Trigger object never fires (i.e. returns
    * {@code null} from {@link Trigger#nextExecutionTime})
    * @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
    * for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
    * @see org.springframework.scheduling.support.CronTrigger
    */
   @Nullable
   ScheduledFuture<?> schedule(Runnable task, Trigger trigger);

前面的代碼可以看到,我們?cè)陬愔凶⑷肓诉@個(gè)類,但是他是接口,我們?cè)趺粗朗悄莻€(gè)實(shí)現(xiàn)類呢,以往出現(xiàn)這種情況要在類上面加@Primany或者@Quality來(lái)執(zhí)行實(shí)現(xiàn)的類,但是我們看到我的注入上并沒(méi)有標(biāo)記,因?yàn)槭峭ㄟ^(guò)另一種方式實(shí)現(xiàn)的

@Configuration
public class SchedulingConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 定時(shí)任務(wù)執(zhí)行線程池核心線程數(shù)
        taskScheduler.setPoolSize(4);
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        return taskScheduler;
    }
}

在spring初始化時(shí)就注冊(cè)了Bean TaskScheduler,而我們可以看到他的實(shí)現(xiàn)是ThreadPoolTaskScheduler,在網(wǎng)上的資料中有人說(shuō)ThreadPoolTaskScheduler是TaskScheduler的默認(rèn)實(shí)現(xiàn)類,其實(shí)不是,還是需要我們?nèi)ブ付ǎ@種方式,當(dāng)我們想替換實(shí)現(xiàn)時(shí),只需要修改配置類就行了,很靈活。

而為什么說(shuō)他是更優(yōu)雅的實(shí)現(xiàn)方式呢,因?yàn)樗暮诵囊彩峭ㄟ^(guò)ScheduledThreadPoolExecutor來(lái)實(shí)現(xiàn)的

public ScheduledExecutorService getScheduledExecutor() throws IllegalStateException {
   Assert.state(this.scheduledExecutor != null, "ThreadPoolTaskScheduler not initialized");
   return this.scheduledExecutor;
}

三 多節(jié)點(diǎn)任務(wù)執(zhí)行問(wèn)題

這次的實(shí)現(xiàn)過(guò)程中,我并沒(méi)有選擇xxl-job來(lái)進(jìn)行實(shí)現(xiàn),而是采用了TaskScheduler來(lái)實(shí)現(xiàn),這也產(chǎn)生了一個(gè)問(wèn)題,xxl-job是分布式的程序調(diào)度系統(tǒng),當(dāng)想要執(zhí)行定時(shí)任務(wù)的應(yīng)用使用xxl-job時(shí),無(wú)論應(yīng)用程序中部署多少個(gè)節(jié)點(diǎn),xxl-job只會(huì)選擇其中一個(gè)節(jié)點(diǎn)作為定時(shí)任務(wù)執(zhí)行的節(jié)點(diǎn),從而不會(huì)產(chǎn)生定時(shí)任務(wù)在不同節(jié)點(diǎn)上同時(shí)執(zhí)行,導(dǎo)致重復(fù)執(zhí)行問(wèn)題,而使用TaskScheduler來(lái)實(shí)現(xiàn),就要考慮多節(jié)點(diǎn)重復(fù)執(zhí)行問(wèn)題。當(dāng)然既然有問(wèn)題,就有解決方案

· 方案一 將定時(shí)任務(wù)功能拆出來(lái)單獨(dú)部署,且只部署一個(gè)節(jié)點(diǎn) · 方案二 使用redis setNx的形式,保證同一時(shí)間只有一個(gè)任務(wù)在執(zhí)行

我選擇的是方案二來(lái)執(zhí)行,當(dāng)然還有一些方式也能保證不重復(fù)執(zhí)行,這里就不多說(shuō)了,一下是我的實(shí)現(xiàn)

public void executeTask(Long taskId) {
    if (!redisService.setIfAbsent(String.valueOf(taskId),"1",2L, TimeUnit.SECONDS)) {
        log.info("已有執(zhí)行中定時(shí)發(fā)送短信任務(wù),本次不執(zhí)行!");
        return;
    }

四 后記

其實(shí)定時(shí)任務(wù)應(yīng)該每一個(gè)開(kāi)發(fā)都會(huì)用到的工具,以前并沒(méi)有了解其中的實(shí)現(xiàn),這次的功能開(kāi)發(fā)過(guò)程中也算是對(duì)其內(nèi)涵的進(jìn)一步了解,以后遇到定時(shí)任務(wù)的處理也更清晰,更有效率了。

到此這篇關(guān)于SpringBoot定時(shí)任務(wù)功能詳細(xì)解析的文章就介紹到這了,更多相關(guān)SpringBoot定時(shí)任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot上下文初始器加載過(guò)程詳解

    SpringBoot上下文初始器加載過(guò)程詳解

    這篇文章主要介紹了SpringBoot上下文初始器加載過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • JAVA對(duì)稱加密算法PBE定義與用法實(shí)例分析

    JAVA對(duì)稱加密算法PBE定義與用法實(shí)例分析

    這篇文章主要介紹了JAVA對(duì)稱加密算法PBE定義與用法,結(jié)合實(shí)例形式分析了JAVA對(duì)稱加密算法PBE的概念、原理、定義及使用方法,需要的朋友可以參考下
    2019-09-09
  • 理解Java面向?qū)ο缶幊淘O(shè)計(jì)

    理解Java面向?qū)ο缶幊淘O(shè)計(jì)

    這篇文章主要介紹了理解Java面向?qū)ο缶幊淘O(shè)計(jì),面向?qū)ο缶幊淌且环N編程思維方式和編碼架構(gòu)。下面詳細(xì)內(nèi)容,需要的小伙伴可以參考一下
    2022-01-01
  • Java反射之深入理解

    Java反射之深入理解

    這篇文章主要介紹了Java反射機(jī)制的深入理解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • Springboot 項(xiàng)目讀取Resources目錄下的文件(推薦)

    Springboot 項(xiàng)目讀取Resources目錄下的文件(推薦)

    這篇文章主要介紹了Springboot 項(xiàng)目讀取Resources目錄下的文件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • 解決@DateTimeFormat格式化時(shí)間出錯(cuò)問(wèn)題

    解決@DateTimeFormat格式化時(shí)間出錯(cuò)問(wèn)題

    這篇文章主要介紹了解決@DateTimeFormat格式化時(shí)間出錯(cuò)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • MybatisPlus EntityWrapper如何自定義SQL

    MybatisPlus EntityWrapper如何自定義SQL

    這篇文章主要介紹了MybatisPlus EntityWrapper如何自定義SQL,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Java中final,finally,finalize?有什么區(qū)別

    Java中final,finally,finalize?有什么區(qū)別

    這篇文章主要給大家分享的是?Java中final,finally,finalize?到底有什么區(qū)別,文章圍繞final,finally,finalize的相關(guān)資料展開(kāi)詳細(xì)內(nèi)容,具有一定的參考的價(jià)值,需要的朋友可以參考一下
    2021-11-11
  • Spring MVC 關(guān)于controller的字符編碼問(wèn)題

    Spring MVC 關(guān)于controller的字符編碼問(wèn)題

    在使用springMVC框架構(gòu)建web應(yīng)用,客戶端常會(huì)請(qǐng)求字符串、整型、json等格式的數(shù)據(jù),通常使用@ResponseBody注解使 controller回應(yīng)相應(yīng)的數(shù)據(jù)而不是去渲染某個(gè)頁(yè)面。
    2017-03-03
  • Spring Boot 實(shí)現(xiàn)敏感詞及特殊字符過(guò)濾處理

    Spring Boot 實(shí)現(xiàn)敏感詞及特殊字符過(guò)濾處理

    這篇文章主要介紹了SpringBoot實(shí)現(xiàn)敏感詞及特殊字符過(guò)濾處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06

最新評(píng)論