Only most recent task ExecutorService in Java

Viewed 54

I want to create an ExecutorService in Java which, when given a task will stop and discard it's current task (if there is a current task) and execute the given task. When a new task is given to this ExecutorService it is always because the previous tasks became irrelevant an not worth executing anymore.

Is there a builtin way in Java to do this or should I resort to implementing this behavior myself? Or is there another approach which works better in this case?

1 Answers

This is an interesting problem. It took me a bit deeper into the core ExecutorService implementation. Thanks!


Solving without ExecutorService

From what you have mentioned, you will have atmost one thread executing tasks and atmost one task pending because we are interested only in the last submitted task. Do you really need an ExecutorService for this?

You can just hold the next task in a static AtomicReference field of a POJO object. Since we are interested only in the latest task, task producers may simply replace the object in the AtomicReference. The task consumer can get from this field as soon as the current task execution is done. The field must be:

  • static because there should be only one instance of this field
  • AtomicReference since multiple threads may be trying to set the next task.

Solving using ExecutorService

However, if you still want to go the ExecutorService way, you can try this out. Create a ThreadPoolExecutor with only one thread (core and maximum) and give it a BlockingQueue implementation that "forgets" all its elements as soon as a new one is added.

Here is a sample test code that submits new tasks 100 times. If the previous task has not been taken up for execution, it is discarded. If it has been, then it is executed and the new one is queued.

public class OnlyOneTask{
    public static void main( String[] args ){
        ExecutorService svc = null;
        
        /* A BlockingQueue that immediately "forgets" all tasks it had as soon as a new one is "offered". */
        BlockingQueue<Runnable> Q = new ArrayBlockingQueue<Runnable>( 1 ) {
            private static final long serialVersionUID = 1L;
            
            /* Forget the current task(s) and add the new one
             * TODO These 2 steps may need synchronization. */
            public boolean offer( Runnable e) {
                clear();    
                return super.offer( e );
            }
        };
        
        try {
            /* A ThreadPoolExecutor that uses the queue we created above. */
            svc = new ThreadPoolExecutor( 1, 1, 5000, TimeUnit.MILLISECONDS, Q );
            
            for( int i = 0; i < 100; i++ ) {
                /* Our simple task. */
                int id = i;
                Runnable r = () -> {
                    System.out.print( "|" + id + "|" );
                };
                
                svc.submit( r );
                
                /* A delay generator. Otherwise, tasks will be cleared too fast. */
                System.out.print( " " );
            }
        }
        finally {
            svc.shutdown();
            try{
                svc.awaitTermination( 10, TimeUnit.SECONDS );
            }
            catch( InterruptedException e ){
                e.printStackTrace();
            }
        }
    }
}

This sample class is only to give a sense of what I thought will work. You will certainly need to improve upon the following drawbacks in this implementation:

  • The first task is anyhow executed because it is immediately picked up by the ExecutorService. (This is why the next point becomes important.)
  • Interruptibility/cancellability has to be brought in to the running tasks, if necessary

Another way using ExecutorService and Future.cancel()

This is actually the simplest, if you are checking thread interruption in the task. This is basically the same as above but instead of clear()ing the queue, we simply use Future.cancel() to indicate that we don't need to execute the last task.

public static void main( String[] args ){
    ExecutorService svc = null;
    
    try {
        /* A single thread executor is enough. */
        svc = Executors.newSingleThreadExecutor();
        
        Future<?> f = null;
        for( int i = 0; i < 100; i++ ) {
            int id = i;
            
            /* Our simple task. */
            Runnable r = () -> {
                /* If the thread has been interrupted (by the Future.cancel() call, then return from here. */
                if( Thread.currentThread().isInterrupted() ) return;
                System.out.print( "|" + id + "|" );
            };
            
            if( f != null ) f.cancel( true );
            f = svc.submit( r );
            
            /* A pseudo delay generator. */
            System.out.print( " " );
        }
    }
    finally {
        svc.shutdown();
        try{
            svc.awaitTermination( 10, TimeUnit.SECONDS );
        }
        catch( InterruptedException e ){
            e.printStackTrace();
        }
    }
}
Related