Running on Java 17-ea+15-1230 (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!

We have three upcoming LIVE virtual classes in April and May 2021:

  1. Refactoring to Streams and Lambdas for US$ 497 on April 6-7 2021 @ 9am-1pm Frankfurt Time.
  2. Extreme Java - Advanced Topics Java 17 Edition for EUR 1299 on April 19-20 2021 @ 9am-5pm Frankfurt Time. (almost sold out)
  3. Design Patterns Deep Dive for US$ 497 on May 11-12 2021 @ 7-11am Los Angeles Time.

My favourite course at the moment is the Refactoring to Streams and Lambdas course. We spend 8 hours ripping apart a 330k LOC application and replacing bits with more modern code. Too much fun! We still have a few places available for next week. The Advanced Topics Course is also very interesting. It is almost sold out though, so please grab the seats if you would like to join.

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 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

 

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 21

Superpack 21 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...

Java Emergency?

If your system is down, we will review it for 15 minutes and give you our findings for just 1 € without any obligation.