欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java定时任务_结合真实案例,清晰梳理几种定时任务的退出「JAVA并发」

程序员文章站 2022-05-23 10:14:18
...
java定时任务_结合真实案例,清晰梳理几种定时任务的退出「JAVA并发」

工作中常常会有定时任务的开发需求,特别是移动端。最近笔者正好有所涉及,鉴于此,结合开发中的案例说明一下几种定时任务的退出

需求说明:定时更新正在生成的文件大小和状态【进行中、失败、完成】,如果文件生成完成,则退出【CoderBaby】

调度可以用Timer 【调用schedule()或者scheduleAtFixedRate()方法实现】或者ScheduledExecutorService 【结合工作中其它的需求,笔者选用此】

ScheduledExecutorService的初始化(线程池):

private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
  • 手动实现——最朴素的方案【通过sleep来控制时间间隔、break来退出】
            scheduledExecutorService.execute(() -> {                long oldCurFileSize = 0;                while(true) {                    try {                        Thread.sleep(updateInternal * 1000);                        long curFileSize = Files.size(Paths.get(TMP_PCAP_PATH + tmpPcapFileName));                        int status = getStatus(tmpPcapFileName);                        if (isFailed(status) || hasNoData(status) || isFinished(status)) {                            break;                        }                        if (curFileSize != oldCurFileSize) {                            updateFileInfo(tmpPcapFileName, curFileSize, 0);                            oldCurFileSize = curFileSize;                        } else {                            updateFileInfo(tmpPcapFileName, curFileSize, 3);                            // 延迟1秒,才能成功更新                            Thread.sleep(1000);                            break;                        }                    } catch (Exception e) {                        logger.warn("Catch exception : " + e.toString());                    }                }            });

注:

updateFileInfo—更新数据库相关记录;

getStatus查询数据库当前记录的状态,判定是否完成或者出现错误;

updateInternal控制定时任务的运行时间间隔(单位为秒)

  • TimerTask【通过cancel来退出】

定义一个内部类继承自TimerTask抽象类

    class ScheduledUpdateTrafficForensics extends TimerTask {        private String tmpPcapFileName;        private long oldCurrentFileSize = 0;        public ScheduledUpdateTrafficForensics(String tmpPcapFileName) {            this.tmpPcapFileName = tmpPcapFileName;        }        public void run() {            try {                long currentFileSize = Files.size(Paths.get(TMP_PCAP_PATH + tmpPcapFileName));                int status = getStatus(tmpPcapFileName);                if (isFailed(status) || hasNoData(status) || isFinished(status)) {                    this.cancel();                }                if (oldCurrentFileSize != currentFileSize) {                    updateFileInfo(tmpPcapFileName, currentFileSize, 0);            oldCurrentFileSize = currentFileSize;                  } else {                    updateFileInfo(tmpPcapFileName, currentFileSize, 3);                    this.cancel();                }            } catch (IOException e) {                logger.warn("Catch exception : " + e.toString());            }        }    }

通过scheduleAtFixedRate接口来调用(设置时间间隔和且第一次执行的延迟时间)

            scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(new ScheduledUpdateTrafficForensics(tmpPcapFileName),                    updateInternal, pcapDownloadStatusUpdateInternal, TimeUnit.SECONDS);
  • ScheduledFuture【通过cancle和读取isCancelled结果来退出】

定义一个内部类实现Runnable接口

     class ScheduledUpdateTrafficForensics implements Runnable {        private String tmpPcapFileName;        private long oldCurrentFileSize = 0;        public ScheduledUpdateTrafficForensics(String tmpPcapFileName) {            this.tmpPcapFileName = tmpPcapFileName;        }        public void run() {            while (!scheduledFuture.isCancelled()) {                try {                    long currentFileSize = Files.size(Paths.get(TMP_PCAP_PATH + tmpPcapFileName));                    int status = getStatus(tmpPcapFileName);                    if (isFailed(status) || hasNoData(status) || isFinished(status) {                        scheduledFuture.cancel(true);                        return;                    }                    if (oldCurrentFileSize != currentFileSzie) {                        updateFileInfo(tmpPcapFileName, currentFileSize, 0);               oldCurrentFileSize = currentFileSize;                    } else {               updateFileInfo(tmpPacpFileName, currentFileSize, 3);               scheduleFuture.canle(true);            }                } catch (IOException e) {                    logger.warn("Catch exception : " + e.toString());                }            }        }    }

通过scheduleAtFixedRate接口来调用(设置时间间隔和且第一次执行的延迟时间),并且将结果返回给ScheduledFuture

            scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(new ScheduledUpdateTrafficForensics(tmpPcapFileName),                    updateInternal, pcapDownloadStatusUpdateInternal, TimeUnit.SECONDS);

注:

通过scheduledFuture.cancel(true)后可能不能成功结束定时任务,所以必须通过手动调用isCancelled()来判断是否被cancle(调用cancel后,再调用isCancelled() 【一定会返回true】)掉了,然后退出任务。相关源码注释如下:

     * 

After this method returns, subsequent calls to {@link #isDone} will * always return {@code true}. Subsequent calls to {@link #isCancelled} * will always return {@code true} if this method returned {@code true}.

特别说明:

关于schedule(时间基准:运行的实际时间)和scheduleAtFixedRate(时间基准:理论时间点)的区别:

  • scheduleAtFixedRate调度一个task,在delay(ms)后开始调度,然后每经过period(ms)再次调度,貌似和方法—schedule是一样的,其实不然。
  • schedule在计算下一次执行的时间的时候,是通过当前时间(在任务执行前得到) + 时间片,而scheduleAtFixedRate方法是通过当前需要执行的时间(也就是计算出现在应该执行的时间)+ 时间片,前者是运行的实际时间,而后者是理论时间点。

例如:schedule时间片是5s,那么理论上会在5、10、15、20这些时间片被调度,但是如果由于某些CPU征用导致未被调度,假如等到第8s才被第一次调度,那么schedule方法计算出来的下一次时间应该是第13s而不是第10s,这样有可能下次就越到20s后而被少调度一次或多次,而scheduleAtFixedRate方法就是每次理论计算出下一次需要调度的时间用以排序,若第8s被调度,那么计算出应该是第10s,所以它距离当前时间是2s,那么再调度队列排序中,会被优先调度,那么就尽量减少漏掉调度的情况。


同样的,我还整理了一部分其他的学习资料,相应源码文档正逐步上传到git中,需要我的git地址的,关注+转发后,私信【git】即可查看地址

相关标签: java定时任务