views:

104

answers:

3

Let's say you have N runnable objects, and for each you want them to execute for random periods of time. Once a runnable object executes for that time period, you want to reschedule it to run for another random period of time. You want to be able to do this for each of the runnables, multiple times.

Once a runnable is started, it should just perform something in an indefinite loop - that is the runnable should have no knowledge of how long it will run. From the runnable's point of view, it will run indefinitely.

How can this be achieved, ideally using only the standard Java API? If this not possible to achieve as is, what alternate design would be closest?

+2  A: 

After all.. you have to use a TimerTask in combination with a Timer. I hope this is the final installment :)!

You should try something like this:

public final class TaskManager
{    

    private Timer _timer;
    private final ArrayList<Semaphore> _permits;
    private final ExecutorService _threadPool;
    public TaskManager(int numTasks)
    {
        _timer = new Timer()
        _permits = new ArrayList<Semaphore>();
        _threadPool = Executors.newFixedThreadPool(numTasks);
        for(int i = 0; i < numTasks; ++i)
        {
            Semaphore available = new Semaphore(1);
            _permits.add(available);

            // execute the task
            _threadPool.execute(new Runnable(){
                public void run(){
                    // run the task
                    (new SampleTask(available)).run();

                    // schedule the task to be stopped after some delay
                    _timer.schedule(new TimerTask(){ 
                        public void run() {
                            // Stops the task
                            available.acquire();
                        } 
                   }, /*SOME_RANDOM_DELAY*/;);
                } 
            });


        }
    }

    public void run()
    {
        while(true)
        {
            for(Semaphore available: _permits)
            {
                int delay = /*RANDOM_DELAY*/;

                Semaphore permit = available;

                // Allows the task to work
                permit.release();

                // Schedules when to stop the task
                _timer.schedule(new TimerTask(){ 
                    public void run() {
                        // Stops the task
                        permit.acquire();
                    } }, delay);

                // perhaps you should do something to ensure that you don't schedule the same permit twice...
            }
        }
    }

}


public final class SampleTask extends Runnable {
    private final Semaphore _available;
    private final TaskManager _taskManager;

    public SampleTask(Semaphore available)
    {
        _available= available;
    }

    // Implements the run method
    public void run()
    {
        while(true)
        {
            // wait till I'm allowed to work
            _available.acquire();

            // pretend like I'm working

            // release the semaphore when finished
            _available.release();
        }

    }
}
Lirik
Can you elaborate? Assuming you have two runnable objects R1 and R2, how do you make R1 run for 2 seconds, then 5, then 1, then 8, etc and R2 for 6, then 2, then 9, then 3, ... and then generalize for random times and random number of executions and random number of runnables?
JRL
@JRL, I threw together some code for you... don't know if it compiles, but it should give you a good idea of how you could achieve what you're trying to achieve.
Lirik
@Lirik: thanks, but in your scenario you are telling the runnable how long to run. Ideally I would want the runnable to be completely unaware of this, that is from the runnable's point of view it runs indefinitely, and at some random time in the future it gets shutdown, then some random time after that, gets restarted, etc.
JRL
@JRL please take a look at the update... I think it does what you need it to do now.
Lirik
@Lirik: not quite, what I'm looking for would be for SampleTask to not have any trace of the timePeriod (it would just be a dumb runnable with no knowledge of time whatsoever)... So really all the work should be done by the executor.
JRL
@JRL, OK :) LOL Final installment... please see this code: the TaskManager controls how long a task will run and the the SampleTask doesn't know anything about the time.
Lirik
+1  A: 

Yeap, I think it is possible. You just have to keep somewhere the deadline for the task and then using a Timer you can just check if the given TimerTask should keep running or the timer should be cancelled.

Here's a complete example. It's far from perfect but just is the proof of concept on how should it work.

For a list of Runnable, you start a new ExecuteTask instance, which internally knows if it should run again, or if they have reach the dead line.

Notice the Runnables are not aware if they would run forever or not at all.

In the code below, I reschedule every second, and the random is within a 10 secs range, but you can reschedule every millisecond, and up to whatever time is reasonable for you.

For instance:

 Execute task = new ExecuteTask( new Runnable(){
      public void run(){
          System.out.println("Hi");
       }
  }); 
  task.start(); // would run for "random"  seconds.... 

I hope , I have understood what you need.

import java.util.*;
import static java.lang.System.currentTimeMillis;
import static java.lang.System.out;

class ScheduledExecutionDemo {
    public static void main( String [] args ) {
        List<Runnable> runnables = Arrays.asList( new Runnable[]{
            new Runnable(){ public void run(){ out.println("I'm the one");}},
            new Runnable(){ public void run(){ out.println("I'm the two");}},
            new Runnable(){ public void run(){ out.println("I'm the three");}},
            new Runnable(){ public void run(){ out.println("I'm the four");}},
        });
        for( Runnable run : runnables ) {
            new ExecuteTask( run ).start();
        }

    }
}
class ExecuteTask  extends TimerTask {

    // This map keeps track on when every task must finish. 
    // Every time a new instance is created it is stored here
    // and every time it is scheduled again checks if it still have time.
    private final static Map<Timer, Long> upTo = new HashMap<Timer, Long>();
    private final static Random random = new Random();

    private final Timer owner;
    private final Runnable task;

    public ExecuteTask(  Runnable task ) {
        this.owner =  new Timer();
        this.task = task;
        upTo.put( owner, currentTimeMillis() + random.nextInt( 10 ) * 1000 );
    }
    public void start() {
        owner.schedule( this, 0 , 1000 );
    }
    public void run() {
        if( shouldRunAgain() ) {
            task.run();
        } else {
            owner.cancel();
        }
    }
    private boolean shouldRunAgain() {
        return ExecuteTask.upTo.get( owner ) > currentTimeMillis();
    }
}

With this proof of concept, you could use a queue and put out the runnables while they are executing and put them back when they have finished their execution, instead of using a simple list.

Also, there might be some synchronization issues, but with the information you provided I think this would be enough.

I hope it helps.

OscarRyz
+1  A: 

You might find this simpler.

ScheduledExecutorService ses = ...
Runnable runnable = ...

new RandomExecutor(ses, runnable, 10, 10);
new RandomExecutor(ses, runnable, 10, 10);

// run for a random length of time and wait for a random length of time, repeat.
public class RandomExecutor implements Runnable {
    private static final Random rand = new Random();
    private ScheduledExecutorService ses;
    private Runnable runnable;
    private int maxRun;
    private int maxSleep;

    public RandomExecutor(ScheduledExecutorService ses, Runnable runnable, int maxRun, int maxSleep) {
        this.ses = ses;
        this.runnable = runnable;
        this.maxRun = maxRun;
        this.maxSleep = maxSleep;
        ses.execute(this);
    }

    @Override
    public void run() {
        long end = System.currentTimeMillis() + rand.nextInt(maxRun);
        do {
            runnable.run();
        } while(end > System.currentTimeMillis());
        ses.schedule(this, rand.nextInt(maxSleep)+1, TimeUnit.MILLISECONDS);
    }
}
Peter Lawrey