228 Extracting Real Task from FutureTask

Author: Dr. Heinz M. Kabutz Date: 2015-04-30 Java Version: 5 Category: Tips and Tricks

Abstract: ExecutorService allows us to submit either Callable or Runnable. Internally, this is converted to a FutureTask, without the possibility of extracting the original task. In this newsletter we look at how we can dig out the information using reflection.

Welcome to the 228th edition of The Java(tm) Specialists' Newsletter. As you probably know, I live on an island in the Mediterranean Sea. And no, I don't own the entire island, just a large enough chunk so that my neighbours generally don't complain about our noise. This is a good thing, as my son started an alternative punk rock band three years ago with his friends. We have been collecting egg boxes from the whole village of Chorafakia to try contain the drumming. In January our little band came first in Greece at the Global Battle of the Bands competition and this past Sunday they competed in the world final in Oslo. Unfortunately I could not attend, but despite their #1 fan not being there, they managed to place 4th in the world. Here is Pull the Curtain, which I watched them produce in Athens. Well done my boy!

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Extracting Real Task from FutureTask

One of the annoyances of the ThreadPoolExecutor and its subclass ScheduledThreadPoolExecutor, is that the tasks that we submit are wrapped with a FutureTask without a possibility of us getting the original task again. I wrote about this in newsletter 154, where I tackled the challenge of resubmitting failed timed tasks.

However, in this newsletter, I would like to show a small class that can extract the Runnable or Callable from the FutureTask that is returned from the ThreadPoolExecutor. Before we get into the details, here are the various ways I can think of where you might see a FutureTask wrapper:

When you call shutdownNow(), a Collection of Runnables is returned. These are not the Runnables that we submitted, but the FutureTask wrapper objects. If you specify a RejectedExecutionHandler, the Runnable that is passed into the rejectedExecution() method is again the FutureTask wrapper. The beforeExecute() and afterExecute() methods that you can override again provide you with the wrapper object.

The FutureTask always contains a Callable. If we submit a Runnable to the ThreadPoolExecutor, it wraps this with a Callable adapter using the Executors.callable(Runnable) method. I make certain assumptions in my JobDiscoverer that could certainly not be true on other implementations of the FutureTask. I assume that it contains a field called "callable". Furthermore I assume that if the type is the same as what we would get from calling Executors.callable(), that initially a Runnable has been passed into the ThreadPoolExecutor. I can think of quite a few scenarios in which this assumption is false.

Here is my JobDiscoverer class, which uses reflection to find out what the type is. Since the result can be either a Runnable or a Callable, I need to return Object. The code is relatively straightforward:

import java.lang.reflect.*; import java.util.concurrent.*; public class JobDiscoverer { private final static Field callableInFutureTask; private static final Class<? extends Callable> adapterClass; private static final Field runnableInAdapter; static { try { callableInFutureTask = FutureTask. class .getDeclaredField( "callable" ); callableInFutureTask.setAccessible( true ); adapterClass = Executors.callable( new Runnable() { public void run() { } }).getClass(); runnableInAdapter = adapterClass.getDeclaredField( "task" ); runnableInAdapter.setAccessible( true ); } catch (NoSuchFieldException e) { throw new ExceptionInInitializerError(e); } } public static Object findRealTask(Runnable task) { if (task instanceof FutureTask) { try { Object callable = callableInFutureTask.get(task); if (adapterClass.isInstance(callable)) { return runnableInAdapter.get(callable); } else { return callable; } } catch (IllegalAccessException e) { throw new IllegalStateException(e); } } throw new ClassCastException( "Not a FutureTask" ); } }

In my sample code, I submit ten jobs to an ExecutorService, both Runnable and Callable interleaved. Each of the tasks would block indefinitely. They also have overridden the toString() method to return the type of class they are. I then call shutdownNow() and first print out the values in the result list, followed by what our JobDiscoverer returns. Again, the code is fairly simple. The one thing that might be puzzling is that some of the threads show that they were interrupted, and others don't. I will leave that as an exercise to the reader to figure out :-) [It's also not difficult.]

import java.util.*; import java.util.concurrent.*; public class JobDiscovererTest { public static void main(String... args) { final CountDownLatch latch = new CountDownLatch( 1 ); ExecutorService pool = Executors.newFixedThreadPool( 3 ); for ( int i = 0 ; i < 5 ; i++) { final int finalI = i; pool.submit( new Runnable() { public void run() { try { latch.await(); } catch (InterruptedException consumeAndExit) { System.out.println(Thread.currentThread().getName() + " was interrupted - exiting" ); } } public String toString() { return "Runnable: " + finalI; } }); pool.submit( new Callable<String>() { public String call() throws InterruptedException { latch.await(); return "success" ; } public String toString() { return "Callable: " + finalI; } }); } // Note: the Runnables returned from shutdownNow are NOT // the same objects as we submitted to the pool!!! List<Runnable> tasks = pool.shutdownNow(); System.out.println( "Tasks from ThreadPool" ); System.out.println( "=====================" ); for (Runnable task : tasks) { System.out.println( "Task from ThreadPool " + task); } System.out.println(); System.out.println( "Using our JobDiscoverer" ); System.out.println( "=======================" ); for (Runnable task : tasks) { Object realTask = JobDiscoverer.findRealTask(task); System.out.println( "Real task was actually " + realTask); } } }

The output on my machine is the following:

Tasks from ThreadPool ===================== pool-1-thread-1 was interrupted - exiting pool-1-thread-3 was interrupted - exiting Task from ThreadPool java.util.concurrent.FutureTask@5a07e868 Task from ThreadPool java.util.concurrent.FutureTask@76ed5528 Task from ThreadPool java.util.concurrent.FutureTask@2c7b84de Task from ThreadPool java.util.concurrent.FutureTask@3fee733d Task from ThreadPool java.util.concurrent.FutureTask@5acf9800 Task from ThreadPool java.util.concurrent.FutureTask@4617c264 Task from ThreadPool java.util.concurrent.FutureTask@36baf30c Using our JobDiscoverer ======================= Real task was actually Callable: 1 Real task was actually Runnable: 2 Real task was actually Callable: 2 Real task was actually Runnable: 3 Real task was actually Callable: 3 Real task was actually Runnable: 4 Real task was actually Callable: 4

Short and sweet newsletter I hope? :-) Sent from Athens International Airport.

Kind regards

Heinz

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

Load Disqus comments

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Please enable JavaScript to view the comments powered by Disqus.

Related Articles

263 Anonymous Tuples 2018-11-28 Anonymous inner classes can be used effectively in Java 8 Streams to create tuples. See how this can improve our refactoring of old procedural code into the functional paradigm. Full Article

254 Big O Cost of Class.getMethod() 2018-02-27 We now look at why the best-case scenario for a getMethod() call is O(n), not O(1) as we would expect. We also discover that the throughput of getMethod() has doubled in Java 9. Full Article

154 ResubmittingScheduledPoolExecutor 2007-12-04 Timers in Java have suffered from the typical Command Pattern characteristics. Exceptions could stop the timer altogether and even with the new ScheduledPoolExecutor, a task that fails is cancelled. In this newsletter we explore how we could reschedule periodic tasks automatically. Full Article

Browse the Newsletter Archive