java.util.Timer 调度程序每天都会出错,因为夏令时
我下面的代码运行良好,该作业每天都会在正确的时间执行,但夏令时更改除外。它发生在十月底和三月。我的服务器有一个轻量级硬件,它位于使用它的区域。 我怎样才能让它工作,使用最少的资源(没有石英或任何类似的)
// 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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
tl;dr
仅使用 java.time 类,切勿使用
Date
/Calendar
。白天的长度各不相同,并不总是 24 小时。因此,在一天中的某个时间跑步意味着确定每天的特定时刻。
过时的类
您正在使用糟糕的日期时间类,这些类在几年前就被 JSR 310 中定义的现代 java.time 类所取代
。 oracle.com/en/java/javase/17/docs/api/java.base/java/util/Timer.html" rel="noreferrer">
Timer
被取代的类几年前由执行器框架,如 Javadoc 中所述。现代 Java
计划执行程序服务
在应用程序的某个位置,初始化计划执行程序服务。
保留预定的执行者服务。请务必在应用程序退出之前将其关闭,否则其后备线程池可能会像僵尸一样继续运行
tl;dr
Use only java.time classes, never
Date
/Calendar
.Days vary in length, not always 24 hours long. So, running at a certain time-of-day means determining a particular moment for each and every day.
Outmoded classes
You are using terrible date-time classes that were years ago supplanted by the modern java.time classes defined in JSR 310.
And you are using the
Timer
class that was supplanted years ago by the Executors framework, as noted in the Javadoc.Modern Java
Scheduled executor service
Somewhere in your app, initialize a scheduled executor service.
Keep that scheduled executor service around. Be sure to shut it down before your app exits as otherwise its backing pool of threads may continue running like a zombie ????♂️.
Runnable
taskDefine your task as a
Runnable
orCallable
.Days vary in length
You want your task to run once per day at a designated time-of-day as seen in a particular time zone.
That does not mean every 24 hours. On some dates in some zones, days vary in length. They may be 23 hours, 25 hours, or 23.5 or some other number of hours long.
Task re-schedules itself
To determine when to run the task next, the task itself should make that calculation. The task will then re-schedule itself for its next execution. To do this, our task must keep a reference to the scheduled executor service on which it is executing. And our task must track the time of day at which you want to run. And the task must track the time zone in which we are to perceive this time-of-day. So we need to pass this info to the constructor of our task, and remember the values as private members.
To simulate work, in this example we simply print to console. We do this in the
run
method promised by theRunnable
interface.Calculating next run
To that
run
method we must add the calculation of the next run. First we capture the current date just in case the clock were to run over to the next date while performing our task’s workload.Next we calculate the amount of time until the next run.
Scheduling task to run on executor service
Lastly we schedule that work.
To get this task running the very first time, the calling app will need to schedule this task this first time. Rather than make the calling app do the duration calculation, let's make the duration calculation code seen here available as a method.
We have a subtle bug in that method. We should not assume the next run is for tomorrow. If, on the very first run, the current moment is not yet past the target time-of-day then we should not add a day to the current date. We should only call
.plusDays( 1 )
if the time-of-day has passed for the current date.Final code
Putting all that code together looks like this.
Demo app
Write a little demo app to use it.
To make this demo work without us having to wait an entire day, let's calculate a minute from the current moment as our desired time-of-day. Then we need only wait a minute to see this code work.
Actually, we will wait two minutes, enough time to ensure our principal code has completely finished before we shut down the demo app’s scheduled executor service.
When run.
Not so S.O.L.I.D.
The code seen above is workable. I could imagine putting that into production. However, looking at the bigger picture, that code has a design flaw.
The SOLID principles are generally recognized as a way of designing better software. The
S
stands for Single-responsibility principle. That means a class should focus on doing one main thing, and doing that one thing well. So different jobs should be handled by different classes.But in the code seen above we are mixing two different jobs.
We have the original job, the task that needs to be performed routinely. Imagine that task is some business function such as compiling a Sales report, or calculating an accounting roll-up, or syncing inventory tallies. Such functions do not really care about when the run; they care about sales figures, accounting numbers, or inventory levels.
Deciding when to run such a function is a different kind of job. This other job of capturing the current moment, applying time zones, calculating time to elapse, is entirely separate from sales, accounting, or inventory.
Even the name of our
Runnable
class is a clue to this violation of the Single-Responsibility Principle:DailyTask
has two parts,Task
referring to the business function to be accomplished, andDaily
referring to the scheduling chore.So ideally, rather than have our task reschedule itself, we should have two different classes. One handles the original job of business work, and the other handles the scheduling of execution.
Implementing this goes far beyond the original Question. So I will leave this as an exercise for the reader. ;-)