Brian Lam


Software Engineer

Scheduling Tasks In Java

It’s been awhile. I just thought I’d do something a little different to add a little bit of variety to this blog. We’ll probably be seeing more additions in the future.

Lately I’ve been working on one of my side-projects where I am updating and improving an existing MapleStory emulator. One of the issues that arose from the existing legacy code caused a key feature in the game to be broken. Without going too deeply about how the game works, here is an explanation in layman’s terms. When a player attacks a monster, certain skills are able to apply a damage over time. Damage over time is an example of scheduling tasks which is a common thing that is done in many programs. We’ll be working towards this!

First off, we must consider on how this could be done. Obviously this cannot be done in the main thread thread as this would completely block it until the timer goes off. We will require multiple background threads for this to be done. Let’s look around in the Java API to see what is available to us.

From a quick look, I found something called Timer which seems to do what what we are looking for. Let’s try to build something simple like scheduling a task to go off in 5 seconds. Looking at the Java API page for Timer, this method looks appropriate. Let’s start writing the code. From that method signature, we need a TimerTask. We will create a “dummy task” to extend off TimerTask. Here is mine.

Now for a class that will act as the driver to create a Timer and run our TimerTask, it should look something like this:

package timer;
 
import java.util.Timer;
 
public class TimerTest {
    public static void main(String[] args) {
        Timer timer = new Timer();
        DummyTask dt = new DummyTask();
        // Schedules the task to run in 5000 milliseconds (or 5 seconds)
        timer.schedule(dt, 5000);
    }
}

So the following code above will run our DummyTask once in 5000 milliseconds (or 5 seconds). Let’s try it!

config.yml

Hurray! It works. Looks great, right? But wait. There is a small problem with using the Timer class. First of all, each Timer object corresponds to one Timer thread. This may not be a problem for a small project, but in my case, I was maintaining an emulator which has multiple timers that occur concurrently. This isn’t sustainable. It would be ideal for us to have a thread pool which is a group of idle threads used for just executing tasks. Let’s take a look at this problem again with a different approach and let’s try to make the task repeat every 1 second (this is a simple change. We’ll just use the scheduleAtFixedRate).

After a quick look through the Java API again, I found something called ScheduledThreadPoolExecutor which does exactly just that. It is functionally the same as a Timer but with a thread pool. So let’s get to implementing this with ScheduledThreadPoolExecutor instead. First, we’ll have to set a pool size. Depending on your usage, you may want a large pool size. For our example, we’ll be using a thread pool of size 1 since we won’t be creating a lot of scheduled tasks. The code changes here are relatively small since Timer and ScheduledThreadPoolExecutor are virtually identical in method signatures. The major differences are that the DummyTask now implements Runnable instead of extending of TimerTask (but TimerTask implemented from Runnable in the first place). You can check out the driver class that creates the scheduled task with a “timer” using ScheduledThreadPoolExecutor and the DummyTask below:

package timer;

import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TimerTest {
	public static void main(String[] args) {
		ScheduledThreadPoolExecutor stpe =  new ScheduledThreadPoolExecutor(1);
		DummyTask dummyTask = new DummyTask();
		
		// Schedules the task to run every 1 second
		ScheduledFuture<?> scheduledDummyTask = stpe.scheduleAtFixedRate(dummyTask, 1, 1, TimeUnit.SECONDS);
  }
}
package timer;
 
public class DummyTask implements Runnable {
 
    @Override
    public void run() {
        System.out.println("I AM ALIVE!!!!");
    }  
}

Okay. So up to this point, we have a timer that runs every 1 second using a thread pool, but there is one problem. The timer doesn’t stop. What gives? Well, you will need to cancel this task. In order to do this, you must get the ScheduledFuture object returned after you schedule a task with ScheduledThreadPoolExecutor, and you’ll need another task to cancel it using another timer. However, this action is only done once, since we need to stop this action only once. Therefore, we should be scheduling it using the schedule method. So we’ll need a new class to cancel the dummy task. We’ll call it DummyCancelTask and it should cancel the ScheduledFuture object that is passed into it. It should look something like this.

package timer;

import java.util.concurrent.ScheduledFuture;

public class DummyCancelTask implements Runnable {
	private ScheduledFuture<?> dummyTaskTimer;
	
	public DummyCancelTask(ScheduledFuture<?> dummyTaskTimer) {
		this.dummyTaskTimer = dummyTaskTimer;
	}
	
	@Override
	public void run() {
		// Cancels the task even if it is in the middle of it
		System.out.println("I AM DEAD!!!");
		dummyTaskTimer.cancel(true);
	}
}

Now, we need to make the changes to our driver class to schedule the cancel task. Note that you should also cancel your scheduled cancel task when it is done. You can see the code for this here.

package timer;

import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TimerTest {
	public static void main(String[] args) {
		ScheduledThreadPoolExecutor stpe =  new ScheduledThreadPoolExecutor(1);
		DummyTask dummyTask = new DummyTask();
		
		// Schedules the task to run every 1 seconds
		ScheduledFuture<?> scheduledDummyTask = stpe.scheduleAtFixedRate(dummyTask, 1, 1, TimeUnit.SECONDS);
		
		// Schedules the cancel task to occur in 5 seconds
		DummyCancelTask dummyCancelTask = new DummyCancelTask(scheduledDummyTask);
		ScheduledFuture<?> scheduledDummyCancelTask = stpe.schedule(dummyCancelTask, 5, TimeUnit.SECONDS);
		
    try {
      if (scheduledDummyCancelTask.get() == null) {
        System.out.println("Task has been cancelled");
        scheduledDummyCancelTask.cancel(true);
        System.exit(0);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

And it is done! But wait, there could be a potential problem. Can you see it? No? Let me help you with that. We are saying that the tasks are cancelled, but they are actually not removed from the work queue that ScheduledThreadPoolExecutor has. If you are interested in monitoring your scheduled tasks, this may not be an issue, but if you’d like it to be removed, you must set be interested in setting the boolean in setRemoveOnCancelPolicy to true before you actually schedule any tasks.

Written on March 3, 2019
< < Go back