5

I am trying to create a Timer/TimerTask that will run the same day of every month. I can't schedule a repeating Timer because a month won't always be the same lenght of time.

So, here is my solution:

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
    }
}

The problem I think I'm going to run into is that because the first TimerTask creates the second Timer, will the first Timer be kept around because it created the second? After the code finishes on the first Timer, will that thread and object be taken care of by garbage collection? Over time I don't want to build up a bunch of Threads that aren't doing anything but aren't being removed. Maybe I don't have a proper understanding of how Threads and Timers work...

I'm open to suggestions of other ways to create a monthly timer as long as I don't have to use third party JARs.

Thanks!

bmeding
  • 617
  • 7
  • 14

7 Answers7

5

I would suggest simply using Quartz and scheduling jobs via a CronTrigger which will allow you to specify you want the job executed on the first day-of-the-month, and let Quartz handle the timing logic.

Here is a further code example of how to use CronTrigger.

Quartz is a dead-simple easy library to use.

matt b
  • 138,234
  • 66
  • 282
  • 345
  • 2
    I thought the OP said: " as long as I don't have to use third party JARs." – Joel Nov 05 '10 at 15:03
  • 4
    "Help me solve the same problems someone else has solved better, previously." – Shawn D. Nov 05 '10 at 15:17
  • The original version of this question said "as long as I don't have to include a bunch of use third party JARs". Outside of slf4j, the basic features of Quartz seems to have no other external dependencies. I wouldn't consider two JARs - which total maybe 500k in size - to be "a bunch". Unless you're working in some sort of embedded or otherwise constrained environment, this type of concerns reeks of NIH syndrome. – matt b Nov 05 '10 at 15:24
  • Sorry about the edit, I changed it once I noticed almost every answer was to use third party software. I don't get to pick my requirements... – bmeding Nov 05 '10 at 15:38
5

If what worries you is to create unneeded objects you can alway create an object which in turn creates/"destroy" all the references, so the objects created may be gc'ed.

In the worst case, you'll have 12 unneeded objects in a year, which, I think is bearable. Still your concern is valid.

Here's my attempt following Joel's suggestion of schedule at the end of the execution. Notice, the current Timer is replaced by a new one, so, both, the timer and the timer task could be gc'ed.

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();

    }
}
OscarRyz
  • 196,001
  • 113
  • 385
  • 569
  • Its not so much the timer object being left, its the Thread that the timer created. Even with your example, my Eclipse debugger says the Threads are still running. Is that a big deal to have those Threads still out there? – bmeding Nov 05 '10 at 18:48
  • Oh I see. There will be 1 thread per scheduled task. Which is not that much either. Tell us what you did at the end would you? – OscarRyz Nov 05 '10 at 20:42
  • Ahh, I did some more testing with your example. If I cancel and purge the old Timer before creating a new instance in the schedule method it is also removing the Threads. I realize that leaving them there isn't that big of a deal but this is also a learning experience for me. Thanks! – bmeding Nov 05 '10 at 21:13
  • Interesting!! I changed the code to cancel before scheduling. – OscarRyz Nov 05 '10 at 21:49
4

What about just using a scheduled timer, and as you complete the currently scheduled task schedule the next:

 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);
  }
 }
}

You can use JodaTime to do the date calculations more easily.

Joel
  • 29,538
  • 35
  • 110
  • 138
  • 1
    +1 for Scheduling at the end of the execution. -1 For suggesting Thread.sleep ( when scheduling at the end of execution is enough ) – OscarRyz Nov 05 '10 at 15:22
  • Oh thanks for pointing that out - that thread will be rendered useless the whole time it is asleep....deleting that suggestion. – Joel Nov 05 '10 at 15:29
1

the simplest solution might be to use cron or equivalent to schedule a stand-alone program execution...

hvgotcodes
  • 118,147
  • 33
  • 203
  • 236
0

Why do you need to recreate Timer every time? Timer is just thread with glue code around. Cancelling it cause terminating of other tasks running on that Timer.

It is better to use the following:

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 just executes MonthlyTimer.taskCallback after original task execution. Could have "try { } catch {} finally {}" glue code.

dernasherbrezon
  • 848
  • 7
  • 16
0

The Quartz scheduler library allows you to schedule based on cron job expressions.

Kaleb Brasee
  • 51,193
  • 8
  • 108
  • 113
0

I think you could also create one single thread and read from DelayQueue to do this. But it's not as easy as ScheduledExecutorService.

codeplay
  • 610
  • 1
  • 9
  • 19
  • moreover, it seems you just want to keep a reference of only one timer since you are calling "timersTable = new HashTable();" each time you restart it, so the HashTable is not really needed here. – codeplay Nov 05 '10 at 15:30
  • You're right, the HashTable isn't needed in the example. My actual code is a littl different though, more of a copy/paste fail on that one. – bmeding Nov 05 '10 at 15:48