Running on Java 24-ea+24-2960 (Preview)
Home of The JavaSpecialists' Newsletter

228Extracting Real Task from FutureTask

Author: Dr. Heinz M. KabutzDate: 2015-04-30Java Version: 5Category: 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:

  1. When you call shutdownNow(), a Collection of Runnables is returned. These are not the Runnables that we submitted, but the FutureTask wrapper objects.
  2. If you specify a RejectedExecutionHandler, the Runnable that is passed into the rejectedExecution() method is again the FutureTask wrapper.
  3. 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 static final 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

 

Comments

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)

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

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...