Ежемесячный таймер Java

Я пытаюсь создать 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-файлы.

Спасибо!


person bmeding    schedule 05.11.2010    source источник


Ответы (7)


Если вас беспокоит создание ненужных объектов, вы всегда можете создать объект, который, в свою очередь, создает / «уничтожает» все ссылки, поэтому созданные объекты могут быть проверены 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();

    }
}
person OscarRyz    schedule 05.11.2010
comment
Остается не столько объект таймера, сколько поток, созданный таймером. Даже в вашем примере мой отладчик Eclipse говорит, что потоки все еще работают. Это большое дело, что эти потоки все еще существуют? - person bmeding; 05.11.2010
comment
О, я вижу. Для каждой запланированной задачи будет 1 поток. Что тоже не так уж и много. Расскажите нам, что вы сделали в конце, не так ли? - person OscarRyz; 05.11.2010
comment
Ааа, я провел еще несколько тестов с вашим примером. Если я отменяю и очищаю старый таймер перед созданием нового экземпляра в методе расписания, он также удаляет потоки. Я понимаю, что оставить их там не так уж и сложно, но это также и полезный опыт для меня. Спасибо! - person bmeding; 06.11.2010
comment
Интересный!! Я изменил код, чтобы отменить до планирования. - person OscarRyz; 06.11.2010

Я бы предложил просто использовать Quartz и планировать задания через CronTrigger, который позволит вам указать, что вы хотите, чтобы задание выполнялось в первый день месяца, и позволит Quartz управлять логикой синхронизации.

Вот еще один пример кода использования CronTrigger.

Quartz — очень простая в использовании библиотека.

person matt b    schedule 05.11.2010
comment
Я думал, что ОП сказал: пока мне не нужно использовать сторонние JAR. - person Joel; 05.11.2010
comment
Помогите мне решить те же проблемы, которые кто-то другой решил раньше лучше. - person Shawn D.; 05.11.2010
comment
Первоначальная версия этого вопроса говорила, что мне не нужно включать кучу сторонних JAR-файлов. За исключением slf4j, основные функции Quartz, похоже, не имеют других внешних зависимостей. Я бы не считал два JAR-файла, общий размер которых может составлять 500 КБ, кучей. Если вы не работаете в какой-то встроенной или иным образом ограниченной среде, этот тип проблем попахивает синдромом НИЗ. - person matt b; 05.11.2010
comment
Извините за редактирование, я изменил его, как только заметил, что почти каждый ответ должен был использовать стороннее программное обеспечение. Я не могу выбрать свои требования... - person bmeding; 05.11.2010

Как насчет того, чтобы просто использовать запланированный таймер, и когда вы завершаете текущее запланированное задание, запланируйте следующее:

 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, чтобы упростить вычисления даты.

person Joel    schedule 05.11.2010
comment
+1 за планирование в конце выполнения. -1 За предложение Thread.sleep (когда достаточно планирования в конце выполнения) - person OscarRyz; 05.11.2010
comment
О, спасибо, что указали на это - этот поток будет бесполезен все время, пока он спит .... удаление этого предложения. - person Joel; 05.11.2010

самым простым решением может быть использование cron или его эквивалента для планирования автономного выполнения программы...

person hvgotcodes    schedule 05.11.2010

Библиотека планировщика Quartz позволяет планировать на основе выражений заданий cron.

person Kaleb Brasee    schedule 05.11.2010

Я думаю, вы также можете создать один поток и читать из DelayQueue для этого. Но это не так просто, как ScheduledExecutorService.

person codeplay    schedule 05.11.2010
comment
более того, похоже, вы просто хотите сохранить ссылку только на один таймер, поскольку вы вызываете timersTable = new HashTable(); каждый раз, когда вы перезапускаете его, поэтому HashTable здесь особо не нужен. - person codeplay; 05.11.2010
comment
Вы правы, HashTable в примере не нужна. Мой фактический код немного отличается, в нем больше ошибок копирования/вставки. - person bmeding; 05.11.2010

Зачем вам каждый раз заново создавать 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 {}».

person Andrey Rodionov    schedule 04.07.2012