Я пытаюсь создать Timer/TimerTask, который будет работать в один и тот же день каждого месяца. Я не могу запланировать повторяющийся таймер, потому что месяц не всегда будет одинаковой продолжительности.
Итак, вот мое решение:
public class MyTask extends TimerTask {
public void run(){
//do process file stuff
if(scheduledExecutionTime() != 0){
TimerHelper.restartMyTimer();
}
}
}
public class TimerHelper {
public static HashTable timersTable = new HashTable();
public static void restartMyTimer(){
Calendar runDate = Calendar.getInstance();
runDate.set(Calendar.DAY_OF_MONTH, 1);
runDate.set(Calendar.HOUR_OF_DAY, 4);
runDate.set(Calendar.MINUTE, 0);
runDate.add(Calendar.MONTH, 1);//set to next month
MyTask myTask = new MyTask();
Timer myTimer = new Timer();
myTimer.schedule(myTask, runDate.getTime());
timersTable = new HashTable();//keeping a reference to the timer so we
timersTable.put("1", myTimer);//have the option to cancel it later
}
}
Проблема, с которой, я думаю, я столкнусь, заключается в том, что, поскольку первая TimerTask создает второй Timer, будет ли первый Timer сохранен, потому что он создал второй? После того, как код завершится на первом таймере, об этом потоке и объекте позаботится сборка мусора? Со временем я не хочу создавать кучу потоков, которые ничего не делают, но не удаляются. Может быть, у меня нет правильного понимания того, как работают потоки и таймеры...
Я открыт для предложений других способов создания ежемесячного таймера, если мне не нужно использовать сторонние JAR-файлы.
Если вас беспокоит создание ненужных объектов, вы всегда можете создать объект, который, в свою очередь, создает / "уничтожает" все ссылки, поэтому созданные объекты могут быть проверены gc.
В худшем случае у вас будет 12 ненужных объектов в год, что, я думаю, терпимо. Тем не менее ваше беспокойство справедливо.
Вот моя попытка, следуя предложению Джоэла о расписании в конце казни. Обратите внимание, что текущий таймер заменяется новым, поэтому и таймер, и задача таймера могут быть проверены.
package monthly.schedule;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;
import java.util.Calendar;
public class MonthlyTimer {
// What to do
private final Runnable whatToDo;
// when
private final int dayOfMonth;
private final int hourOfDay;
// The current timer
private Timer current = new Timer();//to avoid NPE
public void cancelCurrent() {
current.cancel();// cancel this execution;
current.purge(); // removes the timertask so it can be gc'ed
}
// create a new instance
public static MonthlyTimer schedule( Runnable runnable, int dayOfMonth, int hourOfDay ) {
return new MonthlyTimer( runnable, dayOfMonth, hourOfDay );
}
private MonthlyTimer(Runnable runnable, int day, int hour ) {
this.whatToDo = runnable;
this.dayOfMonth = day;
this.hourOfDay = hour;
schedule();
}
// Schedules the task for execution on next month.
private void schedule() {
// Do you mean like this?
cancelCurrent();
current = new Timer(); // assigning a new instance
// will allow the previous Timer to be gc'ed
current.schedule( new TimerTask() {
public void run() {
try {
whatToDo.run();
} finally {
schedule();// schedule for the next month
}
}
} , nextDate() );
}
// Do the next date stuff
private Date nextDate() {
Calendar runDate = Calendar.getInstance();
runDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
runDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
runDate.set(Calendar.MINUTE, 0);
runDate.add(Calendar.MONTH, 1);//set to next month
return runDate.getTime();
}
}
class UseIt {
public static void main( String [] args ) {
int the1st = 1;
int at16hrs = 16;
MonthlyTimer t = MonthlyTimer.schedule( new Runnable() {
public void run() {
System.out.println( "Hola" );
}}, the1st, at16hrs );
// will print "Hola" every 1st at 16:00 hrs.
// if needed you can cancel with:
t.cancelCurrent();
}
}
Ответ 2 · рейтинг 5 · matt b
Я бы предложил просто использовать Quartz и планировать задания через CronTrigger, который позволит вам указать, что вы хотите, чтобы задание выполнялось в первый день месяца, и позволит Quartz управлять логикой синхронизации.
Вот еще один пример кода использования CronTrigger.
Quartz - очень простая в использовании библиотека.
Ответ 3 · рейтинг 4 · Joel
Как насчет того, чтобы просто использовать запланированный таймер, и когда вы завершаете текущее запланированное задание, запланируйте следующее:
ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor();
es.schedule(new MyTask(), numberOfDaysRemaining(), TimeUnit.DAYS);
class MyTask implements Runnable {
public void run() {
try {
// do it
} finally {
es.schedule(new MyTask(), numberOfDaysRemaining(), TimeUnit.DAYS);
}
}
}
Вы можете использовать JodaTime, чтобы упростить вычисления даты.
Ответ 4 · рейтинг 1 · hvgotcodes
самым простым решением может быть использование cron или его эквивалента для планирования автономного выполнения программы...
Ответ 5 · Kaleb Brasee
Библиотека планировщика Quartz позволяет планировать на основе выражений заданий cron.
Ответ 6 · codeplay
Я думаю, вы также можете создать один поток и читать из DelayQueue для этого. Но это не так просто, как ScheduledExecutorService.
Ответ 7 · Andrey Rodionov
Зачем вам каждый раз заново создавать Timer? Таймер - это просто поток с клеевым кодом. Его отмена приведет к завершению других задач, запущенных на этом таймере.
Лучше использовать следующие:
MonthlyTimer extends Timer {
public void execute(TimerTask task, Date date, int dayOfMonth) {
this.schedule(new TimerTaskWithCallback(task, dayOfMonth, this), date);
}
void taskCallback(TimerTaskWithCallback task) {
this.schedule(new TimerTaskWithCallback(task.getImpl()), nextDate(task.getDayOfMonth())); //next date could be used from Oscar's post.
}
}
TimerTaskWithCallback просто выполняет MonthlyTimer.taskCallback после выполнения исходной задачи. Может иметь связующий код "try { } catch {} finally {}".