Concurrent Programming with Java
Lab Manual, Version 1.0, F. Astha Ekadiyanto,
2002.
Lab 4: Introduction to Threads
A TimerTask is an Object that implements Runnable interface.
This means it has an abstract method run() which can be used to define
a task for a Thread object.
TimerTask is designed specially for the Timer Object. A Timer Object
will create and handle a background thread to execute TimerTasks sequentially
in a scheduled manner.
In order to define a task that is scheduled by Timer, we need to define the task in the void run() method of a TimerTask subClass. In this case, we would like to define a Class that will handle the scheduled task to move a MobileStation to a specific target successively in time.
First of all, since we will be accessing some methods and fields in the VisualMobileSystem
Class such as moveMobileStation() method
and the textarea field, thus we must maintain
a reference to the VisualMobileSystem Object. Sometimes, when
we have to access so many methods and fields in another Class, it is just time
consuming to declare references and provide all the access permission in the
other Class (such as defining readonly methods for acceessing private fields).
It would be very efficient if we could define an innerClass so that all
the fields and methods (even private ones) can be accessed directly without
providing any reference (just use the built-in this reference,
but be careful with some confusions when we have the same name in both innerClass
and its "mother" Class).
Although Java does not support nested methods, it does support nested Classes
or inner Classes. An inner Class is when we define a Class in side another Class.
One of the advantage to declare an inner Class is what has been described above.
Open the VisualMobileSystem Class (this Class should exists already in your BlueJ Project) because we will define this class inside it.
The TimerTask Class is defined in the java.util.TimerTask, so make sure we import this Class.import java.util.TimerTask; public class VisualMobileSystem extends Applet implements MouseListener, MouseMotionListener { //. . . All the existing VisualMobileSystem Class definitionsclass MobileTask extends TimerTask
{ //. . . some Class definitions later } }Defining MobileTask() Constructor
The idea about a MobileTask is an object that executed in a timely manner to move gradually, a MobileStation from its original location to a targetted location. Based on this definition, some fields in the MobileTask object is required. The fields are:
- targetX, targetY : the targetted location coordinate (passed in the constructor's argument)
- originX, originY : the original location coordinate (extracted by the MobileStation when passed as a constructor's argument)
- movingStation : the reference to the MobileStation Object that will be moved by this MobileTask.
- stepX, stepY : define the scale of movement in numbers of pixels (int).
The granularity of movement is defined here. In order to have a smooth animation, we would like to define the granularity of movement to the longest relative distance (if x-axis distance is longer, then stepX is the granularity of movement, otherwise stepY will be the granularity of movement). For more information about why do we need this, please check the so called "Path-Finder" algorithm.The MobileTask Constructor is as follows:
class MobileTask extends TimerTask { int targetX,targetY,originX,originY,stepX,stepY; MobileStation movingStation; MobileTask(MobileStation ms, int x, int y) { targetX = x; targetY = y; originX = (int) ms.getX(); originY = (int) ms.getY(); if ( Math.abs(targetX - originX) > Math.abs(targetY-originY)) { stepX=((targetX < originX)?-1:1); stepY=0; } else { stepX=0; stepY=((targetY < originY)?-1:1); } movingStation = ms; } //. . . some other definitions later }Defining the run() task
The run() task should be the task that we want to repeat periodically. Designing such a task would not be so difficult. Just think as if we are performing a loop process. We will just continue moving the MobileStation until it reached the targetted coordinate. When it does, we would like the MobileStation to be removed from the System by just calling removeMobileStation(MobileStation) (will be defined later).
Moving the MobileStation along the Path require a little bit of algorithm. We can call this algorithm, "The PathFinder" (it is just a name). The idea of PathFinder is simple as shown in the figure below.
When a moving Object is set to move from an origin to the new location, we will just define a linear function for its path as shown in the figure. This function will be used to calculate the corresponding moving Y which is the function of an increasing X value. The increasing X value should be bigger than increasing Y but still small enough for a smooth movement, which we call granularity of movement (in this case one pixel at a time). This can be achieved if the ratio of y-distance versus x-distance; which is the gradient; is less than or equal to 1.
It is possible though, that Y becomes the granularity of movement when the x-distance is less than y-distance. In this condition, if we still use X as the granularity of movement, we would expect that Y would be jumping very fast (since the ratio of y-distance versus x-distance is greater than 1). Thus, it is smoother to use the inverted ratio. In this case the function used will be slightly different. Thus, we should check which one is the granularity of movement before we apply it to the corresponding function. Of course, when the target coordinate is reached, the MobileTask should stop or simply call a cancel() method.
Implementing the movement function may be tricky because we must not work with integers along the function. Furthermore, we should maintain the precision of the dividing operation (by casting one of the values to double will do) and cast back into the integer value at the correct time (should be at the last).
The complete definition of the run() method is as follows:
class MobileTask extends TimerTask { //. . . some previous definitions public void run() { int movingX = (int) movingStation.getX(); int movingY = (int) movingStation.getY(); if ((movingX == targetX)&&(movingY==targetY)) { removeMobileStation(movingStation); //To be defined in VisualMobileSystem Class textarea.append(movingStation.getID() + " at " + movingX +","+movingY+" released.\n"); movingStation=null; repaint(); this.cancel(); //End the thread execution on this TimerTask. } else { movingX += stepX; movingY += stepY; if ( stepX == 0 ) movingX = (int) ( ((double)(targetX-originX)/(targetY-originY))*(movingY-originY)+originX); if ( stepY == 0 ) movingY = (int) ( ((double)(targetY-originY)/(targetX-originX))*(movingX-originX)+originY); moveMobileStation(movingStation,movingX,movingY); } } }