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)
仅使用java.time类,切勿使用Date/ Calendar。
白天的长度各不相同,并不总是 24 小时。因此,在一天中的某个时间跑步意味着确定每天的特定时刻。
\npublic 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}\nRun Code Online (Sandbox Code Playgroud)\n您正在使用可怕的日期时间类,这些类在几年前就被 JSR 310 中定义的现代java.time类所取代。
\n您正在使用Timer几年前被 Executors 框架取代的类,如 Javadoc 中所述。
在应用程序的某个位置,初始化预定的执行器服务。
\nScheduledExecutorService see = Executors.newSingleThreadScheduledExecutor() ;\nRun Code Online (Sandbox Code Playgroud)\n保留预定的执行者服务。请务必在应用程序退出之前将其关闭,否则其后备线程池可能会像僵尸 \xe2\x80\x8d\xe2\x99\x82\xef\xb8\x8f 一样继续运行。
\nRunnable任务将您的任务定义为Runnable或Callable。
您希望任务每天在特定时区的指定时间运行一次。
\npublic class DailyTask implements Runnable { \xe2\x80\xa6 } \nRun Code Online (Sandbox Code Playgroud)\n这并不意味着每 24 小时一次。在某些地区的某些日期,白天的长度会有所不同。它们可能是 23 小时、25 小时、23.5 小时或其他一些小时数。
\n为了确定下一步何时运行任务,任务本身应该进行计算。然后,该任务将重新安排自己的下一次执行。为此,我们的任务必须保留对其正在执行的计划执行程序服务的引用。我们的任务必须跟踪您想要跑步的时间。该任务必须跟踪我们感知一天中的这个时间的时区。因此,我们需要将此信息传递给任务的构造函数,并将这些值记住为私有成员。
\nScheduledExecutorService see = Executors.newSingleThreadScheduledExecutor() ;\nRun Code Online (Sandbox Code Playgroud)\n为了模拟工作,在本例中我们只需打印到控制台。我们在接口run承诺的方法中执行此操作Runnable。
public class DailyTask implements Runnable { \xe2\x80\xa6 } \nRun Code Online (Sandbox Code Playgroud)\n对于该run方法,我们必须添加下一次运行的计算。首先,我们捕获当前日期,以防时钟在执行任务\xe2\x80\x99s 工作负载时运行到下一个日期。
LocalDate today = LocalDate.now( this.zoneId );\nRun 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\nRun Code Online (Sandbox Code Playgroud)\n最后我们安排这项工作。
\nthis.ses.schedule( this , d.toNanos() , TimeUnit.NANOSECONDS );\nRun 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}\nRun Code Online (Sandbox Code Playgroud)\n我们在该方法中存在一个微妙的错误。我们不应该假设下一次运行是在明天。如果在第一次运行时当前时刻尚未超过目标时间,那么我们不应该在当前日期上添加一天。.plusDays( 1 )仅当当前日期的时间已过时,我们才应调用。
LocalDate today = LocalDate.now( this.zoneId );\nRun Code Online (Sandbox Code Playgroud)\n将所有代码放在一起看起来像这样。
\nLocalDate 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 );\nRun Code Online (Sandbox Code Playgroud)\n编写一个小演示应用程序来使用它。
\n为了让这个演示工作而不必等待一整天,让我们计算当前时刻的一分钟作为我们想要的一天中的时间。然后我们只需要等一分钟就可以看到这段代码的工作原理。
\n实际上,我们将等待两分钟,足够的时间来确保我们的主要代码完全完成,然后再关闭演示应用程序\xe2\x80\x99s 预定的执行程序服务。
\nthis.ses.schedule( this , d.toNanos() , TimeUnit.NANOSECONDS );\nRun Code Online (Sandbox Code Playgroud)\n运行时。
\npublic 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}\nRun Code Online (Sandbox Code Playgroud)\n上面看到的代码是可行的。我可以想象将其投入生产。然而,从更大的角度来看,该代码存在设计缺陷。
\nSOLID原则通常被认为是设计更好软件的一种方法。代表单一责任S原则。这意味着一个班级应该专注于做一件主要的事情,并把那件事做好。所以不同的工作应该由不同的类来处理。
但在上面的代码中,我们混合了两个不同的作业。
\n我们有原来的工作,需要例行执行的任务。想象一下该任务是某种业务功能,例如编译销售报告、计算会计汇总或同步库存盘点。此类函数并不真正关心何时运行;他们关心销售数字、会计数字或库存水平。
\n决定何时运行这样的函数是另一项工作。捕捉当前时刻、应用时区、计算经过的时间的其他工作完全独立于销售、会计或库存。
\n甚至我们类的名称Runnable也是违反单一职责原则的线索:DailyTask有两个部分,Task指的是要完成的业务功能,以及Daily指的是调度琐事。
因此,理想情况下,我们应该有两个不同的类,而不是让我们的任务自行重新安排。一个处理业务工作的原始工作,另一个处理执行的调度。
\n实现这一点远远超出了最初的问题。所以我将把这个作为练习留给读者。;-)
\n| 归档时间: |
|
| 查看次数: |
912 次 |
| 最近记录: |