pbootcms网站模板|日韩1区2区|织梦模板||网站源码|日韩1区2区|jquery建站特效-html5模板网

詳解SpringBoot定時任務功能

這篇文章主要介紹了SpringBoot定時任務功能詳細解析,這次的功能開發過程中也算是對其內涵的進一步了解,以后遇到定時任務的處理也更清晰,更有效率了,對SpringBoot定時任務相關知識

一 背景

項目中需要一個可以動態新增定時定時任務的功能,現在項目中使用的是xxl-job定時任務調度系統,但是經過一番對xxl-job功能的了解,發現xxl-job對項目動態新增定時任務,動態刪除定時任務的支持并不是那么好,所以需要自己手動實現一個定時任務的功能

二 動態定時任務調度

1 技術選擇

Timer or ScheduledExecutorService

這兩個都能實現定時任務調度,先看下Timer的定時任務調度

  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");
//首次執行,在當前時間的1秒以后,之后每隔兩秒鐘執行一次
timer.schedule(timerTask,1000L,2000L);

在看下ScheduledThreadPoolExecutor的實現

//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);

兩個都能實現定時任務,那他們的區別呢,使用阿里p3c會給出建議和區別

多線程并行處理定時任務時,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。

從建議上來看,是一定要選擇ScheduledExecutorService了,我們看看源碼看看為什么Timer出現問題會終止執行

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

新建對象時,我們看到開啟了一個線程,那么這個線程在做什么呢?一起看看

class TimerThread extends Thread {
  boolean newTasksMayBeScheduled = true;
  /**
   * 每一件一個任務都是一個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();  // 清除所有任務信息
          }
      }
  }
  /**
   * 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) {
          }
      }
  }
}

我們看到,執行了 mainLoop(),里面是 while (true)方法無限循環,獲取程序中任務對象中的時間和當前時間比對,相同就執行,但是一旦報錯,就會進入finally中清除掉所有任務信息。

這時候我們已經找到了答案,timer是在被實例化后,啟動一個線程,不間斷的循環匹配,來執行任務,他是單線程的,一旦報錯,線程就終止了,所以不會執行后續的任務,而ScheduledThreadPoolExecutor是多線程執行的,就算其中有一個任務報錯了,并不影響其他線程的執行。

2 使用ScheduledThreadPoolExecutor

從上面看,使用ScheduledThreadPoolExecutor還是比較簡單的,但是我們要實現的更優雅一些,所以選擇 TaskScheduler來實現

@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是本次功能實現的核心類,但是他是一個接口

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);

前面的代碼可以看到,我們在類中注入了這個類,但是他是接口,我們怎么知道是那個實現類呢,以往出現這種情況要在類上面加@Primany或者@Quality來執行實現的類,但是我們看到我的注入上并沒有標記,因為是通過另一種方式實現的

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

在spring初始化時就注冊了Bean TaskScheduler,而我們可以看到他的實現是ThreadPoolTaskScheduler,在網上的資料中有人說ThreadPoolTaskScheduler是TaskScheduler的默認實現類,其實不是,還是需要我們去指定,而這種方式,當我們想替換實現時,只需要修改配置類就行了,很靈活。

而為什么說他是更優雅的實現方式呢,因為他的核心也是通過ScheduledThreadPoolExecutor來實現的

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

三 多節點任務執行問題

這次的實現過程中,我并沒有選擇xxl-job來進行實現,而是采用了TaskScheduler來實現,這也產生了一個問題,xxl-job是分布式的程序調度系統,當想要執行定時任務的應用使用xxl-job時,無論應用程序中部署多少個節點,xxl-job只會選擇其中一個節點作為定時任務執行的節點,從而不會產生定時任務在不同節點上同時執行,導致重復執行問題,而使用TaskScheduler來實現,就要考慮多節點重復執行問題。當然既然有問題,就有解決方案

· 方案一 將定時任務功能拆出來單獨部署,且只部署一個節點 · 方案二 使用redis setNx的形式,保證同一時間只有一個任務在執行

我選擇的是方案二來執行,當然還有一些方式也能保證不重復執行,這里就不多說了,一下是我的實現

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

四 后記

其實定時任務應該每一個開發都會用到的工具,以前并沒有了解其中的實現,這次的功能開發過程中也算是對其內涵的進一步了解,以后遇到定時任務的處理也更清晰,更有效率了。

到此這篇關于SpringBoot定時任務功能詳細解析的文章就介紹到這了,更多相關SpringBoot定時任務內容請搜索html5模板網以前的文章希望大家以后多多支持html5模板網!

【網站聲明】本站部分內容來源于互聯網,旨在幫助大家更快的解決問題,如果有圖片或者內容侵犯了您的權益,請聯系我們刪除處理,感謝您的支持!

相關文檔推薦

主站蜘蛛池模板: 蓝莓施肥机,智能施肥机,自动施肥机,水肥一体化项目,水肥一体机厂家,小型施肥机,圣大节水,滴灌施工方案,山东圣大节水科技有限公司官网17864474793 | 动物麻醉机-数显脑立体定位仪-北京易则佳科技有限公司 | 广东护栏厂家-广州护栏网厂家-广东省安麦斯交通设施有限公司 | 高光谱相机-近红外高光谱相机厂家-高光谱成像仪-SINESPEC 赛斯拜克 | 并离网逆变器_高频UPS电源定制_户用储能光伏逆变器厂家-深圳市索克新能源 | 仿古建筑设计-仿古建筑施工-仿古建筑公司-汉匠古建筑设计院 | 搅拌磨|搅拌球磨机|循环磨|循环球磨机-无锡市少宏粉体科技有限公司 | 成都软件开发_OA|ERP|CRM|管理系统定制开发_成都码邻蜀科技 | 辽宁资质代办_辽宁建筑资质办理_辽宁建筑资质延期升级_辽宁中杭资质代办 | 铝合金线槽_铝型材加工_空调挡水板厂家-江阴炜福金属制品有限公司 | 沈阳缠绕包装机厂家直销-沈阳海鹞托盘缠绕包装机价格 | 蔬菜清洗机_环速洗菜机_异物去除清洗机_蔬菜清洗机_商用洗菜机 - 环速科技有限公司 | 螺旋叶片_螺旋叶片成型机_绞龙叶片_莱州源泽机械制造有限公司 | 超声骨密度仪,双能X射线骨密度仪【起草单位】,骨密度检测仪厂家 - 品源医疗(江苏)有限公司 | 重庆轻质隔墙板-重庆安吉升科技有限公司 | 线粒体膜电位荧光探针-细胞膜-标记二抗-上海复申生物科技有限公司 | 北京浩云律师事务所-法律顾问_企业法务_律师顾问_公司顾问 | 河北中仪伟创试验仪器有限公司是专业生产沥青,土工,水泥,混凝土等试验仪器的厂家,咨询电话:13373070969 | 智能化的检漏仪_气密性测试仪_流量测试仪_流阻阻力测试仪_呼吸管快速检漏仪_连接器防水测试仪_车载镜头测试仪_奥图自动化科技 | CPSE安博会 | 上海公司注册-代理记账-招投标审计-上海昆仑扇财税咨询有限公司 上海冠顶工业设备有限公司-隧道炉,烘箱,UV固化机,涂装设备,高温炉,工业机器人生产厂家 | 天助网 - 中小企业全网推广平台_生态整合营销知名服务商_天助网采购优选 | 带锯机|木工带锯机圆木推台锯|跑车带锯机|河北茂业机械制造有限公司| | 校园文化空间设计-数字化|中医文化空间设计-党建|法治廉政主题文化空间施工-山东锐尚文化传播公司 | 六维力传感器_六分量力传感器_模腔压力传感器-南京数智微传感科技有限公司 | 杭州标识标牌|文化墙|展厅|导视|户内外广告|发光字|灯箱|铭阳制作公司 - 杭州标识标牌|文化墙|展厅|导视|户内外广告|发光字|灯箱|铭阳制作公司 | 华溶溶出仪-Memmert稳定箱-上海协烁仪器科技有限公司 | 盘装氧量分析仪-防爆壁挂氧化锆分析仪-安徽吉帆仪表有限公司 | 上海律师事务所_上海刑事律师免费咨询平台-煊宏律师事务所 | 液压扳手-高品质液压扳手供应商 - 液压扳手, 液压扳手供应商, 德国进口液压拉马 | 济南菜鸟驿站广告|青岛快递车车体|社区媒体-抖音|墙体广告-山东揽胜广告传媒有限公司 | 防爆电机-高压防爆电机-ybx4电动机厂家-河南省南洋防爆电机有限公司 | 尼龙PA610树脂,尼龙PA612树脂,尼龙PA1010树脂,透明尼龙-谷骐科技【官网】 | 节流截止放空阀-不锈钢阀门-气动|电动截止阀-鸿华阀门有限公司 | 小学教案模板_中学教师优秀教案_高中教学设计模板_教育巴巴 | 万烁建筑设计院-建筑设计公司加盟,设计院加盟分公司,市政设计加盟 | 上海宿田自动化设备有限公司-双面/平面/单面贴标机 | 不锈钢钢格栅板_热浸锌钢格板_镀锌钢格栅板_钢格栅盖板-格美瑞 | 智能汉显全自动量热仪_微机全自动胶质层指数测定仪-鹤壁市科达仪器仪表有限公司 | 钢板仓,大型钢板仓,钢板库,大型钢板库,粉煤灰钢板仓,螺旋钢板仓,螺旋卷板仓,骨料钢板仓 | ASA膜,ASA共挤料,篷布色母料-青岛未来化学有限公司 |