java.util.Timer 调度程序每天都会出错,因为夏令时

Sor*_*iuc 1 java scheduler dst timezone-offset spring-boot

我下面的代码运行良好,该作业每天都会在正确的时间执行,但夏令时更改除外。它发生在十月底和三月。我的服务器有一个轻量级硬件,它位于使用它的区域。我如何才能使用最少的资源(没有石英或任何类似的资源)使其工作

// init daily scheduler 
final java.util.Timer timer = new java.util.Timer("MyTimer");
final String[] hourMinute = time.split(":");
final String hour = hourMinute[0];
final String minute = hourMinute[1];
String second = "0";
final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour));
calendar.set(Calendar.MINUTE, Integer.parseInt(minute));
calendar.set(Calendar.SECOND, Integer.parseInt(second));
if(calendar.getTime().before(new Date())){
    calendar.add(Calendar.DATE, 1);
}

timer.schedule(job, calendar.getTime(), 
TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS)); // period: 1 day
Run Code Online (Sandbox Code Playgroud)

Bas*_*que 8

太长了;博士

\n

仅使用java.time类,切勿使用Date/ Calendar

\n

白天的长度各不相同,并不总是 24 小时。因此,在一天中的某个时间跑步意味着确定每天的特定时刻。

\n
public void scheduleNextRun ( LocalDate today )\n{\n    LocalDate dateOfNextRun = today;\n    if ( LocalTime.now( this.zoneId ).isAfter( this.timeToExecute ) )\n    {\n        dateOfNextRun = dateOfNextRun.plusDays( 1 );\n    }\n    ZonedDateTime zdt = ZonedDateTime.of( dateOfNextRun , this.timeToExecute , this.zoneId );  // Determine a moment, a point on the timeline.\n    Instant instantOfNextTaskExecution = zdt.toInstant(); // Adjust from time zone to offset of zero hours-minutes-seconds from UTC.\n    Duration d = Duration.between( Instant.now() , instantOfNextTaskExecution );\n    this.ses.schedule( this , d.toNanos() , TimeUnit.NANOSECONDS );\n    System.out.println( "DEBUG - Will run this task after duration of " + d + " at " + zdt );\n}\n
Run Code Online (Sandbox Code Playgroud)\n

过时的课程

\n

您正在使用可怕的日期时间类,这些类在几年前就被 JSR 310 中定义的现代java.time类所取代。

\n

您正在使用Timer几年前被 Executors 框架取代的类,如 Javadoc 中所述。

\n

现代爪哇

\n

预定执行人服务

\n

在应用程序的某个位置,初始化预定的执行器服务。

\n
ScheduledExecutorService see = Executors.newSingleThreadScheduledExecutor() ;\n
Run Code Online (Sandbox Code Playgroud)\n

保留预定的执行者服务。请务必在应用程序退出之前将其关闭,否则其后备线程池可能会像僵尸 \xe2\x80\x8d\xe2\x99\x82\xef\xb8\x8f 一样继续运行。

\n

Runnable任务

\n

将您的任务定义为RunnableCallable

\n

白天的长度各不相同

\n

您希望任务每天在特定时区的指定时间运行一次。

\n
public class DailyTask implements Runnable { \xe2\x80\xa6 } \n
Run Code Online (Sandbox Code Playgroud)\n

这并不意味着每 24 小时一次。在某些地区的某些日期,白天的长度会有所不同。它们可能是 23 小时、25 小时、23.5 小时或其他一些小时数。

\n

任务自行重新安排

\n

为了确定下一步何时运行任务,任务本身应该进行计算。然后,该任务将重新安排自己的下一次执行。为此,我们的任务必须保留对其正在执行的计划执行程序服务的引用。我们的任务必须跟踪您想要跑步的时间。该任务必须跟踪我们感知一天中的这个时间的时区。因此,我们需要将此信息传递给任务的构造函数,并将这些值记住为私有成员。

\n
ScheduledExecutorService see = Executors.newSingleThreadScheduledExecutor() ;\n
Run Code Online (Sandbox Code Playgroud)\n

为了模拟工作,在本例中我们只需打印到控制台。我们在接口run承诺的方法中执行此操作Runnable

\n
public class DailyTask implements Runnable { \xe2\x80\xa6 } \n
Run Code Online (Sandbox Code Playgroud)\n

计算下一次运行

\n

对于该run方法,我们必须添加下一次运行的计算。首先,我们捕获当前日期,以防时钟在执行任务\xe2\x80\x99s 工作负载时运行到下一个日期。

\n
LocalDate today = LocalDate.now( this.zoneId );\n
Run Code Online (Sandbox Code Playgroud)\n

接下来我们计算距离下一次运行的时间。

\n
\xe2\x80\xa6\nprivate final ScheduledExecutorService ses;\nprivate final LocalTime timeToExecute;\nprivate final ZoneId zoneId;\n\npublic DailyTask ( final ScheduledExecutorService ses , final LocalTime timeToExecute , final ZoneId zoneId )\n{\n    this.ses = ses;\n    this.timeToExecute = timeToExecute;\n    this.zoneId = zoneId;\n}\n\xe2\x80\xa6\n
Run Code Online (Sandbox Code Playgroud)\n

调度任务在执行器服务上运行

\n

最后我们安排这项工作。

\n
this.ses.schedule( this , d.toNanos() , TimeUnit.NANOSECONDS );\n
Run Code Online (Sandbox Code Playgroud)\n

为了让这个任务第一次运行,调用应用程序需要第一次安排这个任务。让我们将此处看到的持续时间计算代码作为一种方法提供,而不是让调用应用程序执行持续时间计算。

\n
@Override\npublic void run ( )\n{\n    // Perform the task\xe2\x80\x99s work.\n    System.out.println( Thread.currentThread().getId() + " is running at " + Instant.now() );\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们在该方法中存在一个微妙的错误。我们不应该假设下一次运行是在明天。如果在第一次运行时当前时刻尚未超过目标时间,那么我们应该在当前日期上添加一天。.plusDays( 1 )仅当当前日期的时间已过时,我们才应调用。

\n
LocalDate today = LocalDate.now( this.zoneId );\n
Run Code Online (Sandbox Code Playgroud)\n

最终代码

\n

将所有代码放在一起看起来像这样。

\n
LocalDate tomorrow = today.plusDays( 1  );\nZonedDateTime zdt = ZonedDateTime.of( tomorrow, this.timeToExecute , this.zoneId );  // Determine a moment, a point on the timeline.\nInstant instantOfNextTaskExecution = zdt.toInstant(); // Adjust from time zone to offset of zero hours-minutes-seconds from UTC.\nDuration d = Duration.between( Instant.now() , instantOfNextTaskExecution );\n
Run Code Online (Sandbox Code Playgroud)\n

演示应用程序

\n

编写一个小演示应用程序来使用它。

\n

为了让这个演示工作而不必等待一整天,让我们计算当前时刻的一分钟作为我们想要的一天中的时间。然后我们只需要等一分钟就可以看到这段代码的工作原理。

\n

实际上,我们将等待分钟,足够的时间来确保我们的主要代码完全完成,然后再关闭演示应用程序\xe2\x80\x99s 预定的执行程序服务。

\n
this.ses.schedule( this , d.toNanos() , TimeUnit.NANOSECONDS );\n
Run Code Online (Sandbox Code Playgroud)\n

运行时。

\n
public void scheduleNextRun( LocalDate today ) {\n    LocalDate tomorrow = today.plusDays( 1 );\n    ZonedDateTime zdt = ZonedDateTime.of( tomorrow , this.timeToExecute , this.zoneId );  // Determine a moment, a point on the timeline.\n    Instant instantOfNextTaskExecution = zdt.toInstant(); // Adjust from time zone to offset of zero hours-minutes-seconds from UTC.\n    Duration d = Duration.between( Instant.now() , instantOfNextTaskExecution );\n    this.ses.schedule( this , d.toNanos() , TimeUnit.NANOSECONDS );\n    System.out.println( "DEBUG - Will run this task after duration of " + d + " at " + zdt );\n}\n
Run Code Online (Sandbox Code Playgroud)\n

不那么坚固

\n

上面看到的代码是可行的。我可以想象将其投入生产。然而,从更大的角度来看,该代码存在设计缺陷。

\n

SOLID原则通常被认为是设计更好软件的一种方法。代表单一责任S原则。这意味着一个班级应该专注于做一件主要的事情,并把那件事做好。所以不同的工作应该由不同的类来处理。

\n

但在上面的代码中,我们混合了两个不同的作业。

\n

我们有原来的工作,需要例行执行的任务。想象一下该任务是某种业务功能,例如编译销售报告、计算会计汇总或同步库存盘点。此类函数并不真正关心何时运行;他们关心销售数字、会计数字或库存水平。

\n

决定何时运行这样的函数是另一项工作。捕捉当前时刻、应用时区、计算经过的时间的其他工作完全独立于销售、会计或库存。

\n

甚至我们类的名称Runnable也是违反单一职责原则的线索:DailyTask有两个部分,Task指的是要完成的业务功能,以及Daily指的是调度琐事。

\n

因此,理想情况下,我们应该有两个不同的类,而不是让我们的任务自行重新安排。一个处理业务工作的原始工作,另一个处理执行的调度。

\n

实现这一点远远超出了最初的问题。所以我将把这个作为练习留给读者。;-)

\n