Java ScheduledThreadPoolExecutor:shutdownNow()返回队列中Runnable能否执行?
ScheduledThreadPoolExecutor.shutdownNow()? Yes, you absolutely can run those tasks—but your current approach isn't working because the objects returned by shutdownNow() aren't your original Task instances. They're wrapped, cancelled ScheduledFutureTask objects that won't execute their underlying logic when you call run() directly. Let's break down why this happens and how to fix it.
Why Your Current Code Fails
The returned "Runnables" are cancelled
ScheduledFutureTasks:
When you callshutdownNow(), the executor cancels all pending tasks and returns them asRunnables. These are actually instances ofScheduledFutureTask(a package-private subclass ofFutureTask).ScheduledFutureTask.run()checks if the task is in theNEWstate first. Since the task was cancelled, its state is no longerNEW, so the method exits immediately without running your task.- Even if the state were valid,
ScheduledFutureTaskchecks if the executor is in a runnable state. AftershutdownNow(), the executor is inSTOPmode, so it cancels itself again and returns.
Casting to
FutureTask<Task>causes a NullPointerException:
When you submit aRunnable(like yourTask) viaexecute(), the executor wraps it into aFutureTaskwith anullresult. Callingftask.get()returnsnull, so trying to callftask.get().run()throws an NPE.
How to Run the Original Tasks
You need to extract your original Task instances from the wrapped ScheduledFutureTask objects. Since these wrappers are internal to the JDK, we'll use reflection to access the underlying task. Here's how to modify your code:
// After getting the queue from shutdownNow() ExecutorService ses = Executors.newSingleThreadExecutor(); for (Runnable wrappedTask : queue) { try { // Step 1: Get the Callable from the FutureTask Field callableField = FutureTask.class.getDeclaredField("callable"); callableField.setAccessible(true); Callable<?> callable = (Callable<?>) callableField.get(wrappedTask); // Step 2: Get the original Runnable from the RunnableAdapter Field taskField = callable.getClass().getDeclaredField("task"); taskField.setAccessible(true); Runnable originalTask = (Runnable) taskField.get(callable); // Step 3: Run the original task (either directly or via another executor) System.out.println("Running original task: " + ((Task) originalTask).getName()); ses.execute(originalTask); // Or originalTask.run() for direct execution } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } ses.shutdown(); ses.awaitTermination(5, TimeUnit.SECONDS);
Caveat About Reflection
This approach relies on internal JDK implementation details (like the callable field in FutureTask and the task field in Executors.RunnableAdapter). These could change in future JDK versions, so use it with caution.
Alternative: Track Tasks Explicitly (No Reflection)
If you want to avoid reflection, wrap your tasks in a custom holder before submitting them:
public class TaskHolder implements Runnable { private final Task originalTask; public TaskHolder(Task task) { this.originalTask = task; } @Override public void run() { originalTask.run(); } public Task getOriginalTask() { return originalTask; } }
When submitting tasks:
stpe.execute(new TaskHolder(new Task("Task " + count)));
Then, when extracting tasks from the queue:
for (Runnable wrappedTask : queue) { try { Field callableField = FutureTask.class.getDeclaredField("callable"); callableField.setAccessible(true); Callable<?> callable = (Callable<?>) callableField.get(wrappedTask); Field taskField = callable.getClass().getDeclaredField("task"); taskField.setAccessible(true); TaskHolder holder = (TaskHolder) taskField.get(callable); ses.execute(holder.getOriginalTask()); } catch (Exception e) { e.printStackTrace(); } }
This makes your code more resilient to JDK changes, as you control the wrapper class.
内容的提问来源于stack exchange,提问作者Anastasia

