Edwin Straub
Edwin Straub

Reputation: 1

Why is a Runnable still executed in a pausable thread pool executor after the executor is shut down?

I have a pausable thread pool executor implementation just like in the documentation of the ThreadPoolExecutor class. I have a simple test that does the following:

class PausableThreadPoolExecutor extends ThreadPoolExecutor {
  public static PausableThreadPoolExecutor newSingleThreadExecutor() {
    return new PausableThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>());
  }

  /** isPaused */
  private boolean isPaused;

  /** pauseLock */
  private ReentrantLock pauseLock = new ReentrantLock();

  /** unpaused */
  private Condition unpaused = this.pauseLock.newCondition();

  public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
      TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
  }

  @Override
  protected void beforeExecute(Thread t, Runnable r) {
    super.beforeExecute(t, r);
    this.pauseLock.lock();
    try {
      while (this.isPaused) {
        this.unpaused.await();
      }
    } catch (InterruptedException ie) {
      t.interrupt();
    } finally {
      this.pauseLock.unlock();
    }
  }

  public void pause() {
    this.pauseLock.lock();
    try {
      this.isPaused = true;
    } finally {
      this.pauseLock.unlock();
    }
  }

  public void resume() {
    this.pauseLock.lock();
    try {
      this.isPaused = false;
      this.unpaused.signalAll();
    } finally {
      this.pauseLock.unlock();
    }
  }

  public static void main(String[] args) {
    PausableThreadPoolExecutor p = PausableThreadPoolExecutor.newSingleThreadExecutor();
    p.pause();
    p.execute(new Runnable() {

      public void run() {
        for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
          System.out.println(ste);
        }
      }
    });
    p.shutdownNow();
  }
}

Interestingly the call to shutDownNow will cause the Runnable to run. Is this normal? As I understand the shutDownNow will try to stop the actively executing tasks by interrupting them. But the interrupt seems to wake up the task an execute it. Can someone explain this ?

Upvotes: 0

Views: 445

Answers (1)

Gray
Gray

Reputation: 116888

Interestingly the call to shutDownNow will cause the Runnable to run. Is this normal?

Not sure it is "normal" but it is certainly expected given your code. In your beforeExecute(...) method I see the following:

    this.pauseLock.lock();
    try {
        while (this.isPaused) {
            this.unpaused.await();
        }
    } catch (InterruptedException ie) {
        t.interrupt();
    } finally {
        this.pauseLock.unlock();
    }

The job will lopp waiting for the isPaused boolean to be set to false. However, if the job is interrupted the this.unpaused.await() will throw InterruptedException which breaks out of the while loop, the thread is reinterrupted which is always a good pattern, beforeExecute() returns, and the job is allowed to execute. Interrupting a thread doesn't kill it unless you have specific code to handle the interruption.

If you want to stop the job when it is interrupted then you could throw a RuntimeException in the beforeExecute() handler when you see that the job as been interrupted:

} catch (InterruptedException ie) {
    t.interrupt();
    throw new RuntimeException("Thread was interrupted so don't run");

A cleaner approach might be to check to see if you are interrupted in the run() method and then exit:

public void run() {
   if (Thread.currentThread().isInterrupted()) {
      return;
   }
   ...

Upvotes: 1

Related Questions