4
\$\begingroup\$

As per the Java documentation:

Interrupting a thread that is not alive need not have any effect.

Particularly, interrupting a thread between the call to start() and the invocation of run() has no effect.

So I wrote a Thread which can be interrupted even before it started, so that the interrupted state is communicated once it started:

/**
 * A thread that can be interrupted before actually starting (that is between the moment {@link
 * #start} is called and {@link #run} is invoked.
 */
public final class InterruptableThread extends Thread {
    private final AtomicBoolean mInterrupted = new AtomicBoolean(false);
    private final Object mLock = new Object();
    private final AtomicBoolean mStarted = new AtomicBoolean(false);

    public InterruptableThread(Runnable runnable) {
        super(runnable);
    }

    @Override
    public void run() {
        synchronized (mLock) {
            mStarted.set(true);
            if (mInterrupted.get()) {
                super.interrupt();
            }
        }

        super.run(); // Let the original runnable handle the interruption by itself.
    }

    @Override
    public void interrupt() {
        synchronized (mLock) {
            mInterrupted.set(true);
            if (mStarted.get()) {
                super.interrupt();
            }
        }
    }
}

It seems safe to me, although the AtomicBoolean are maybe not required. But because we're dealing with multithreading here, another point of view is welcome.

I need this in a library with start() and stop() methods which look like:

public void start() {
    synchronized (mStartStopLock) {
        myThread = new InterruptableThread(myRunnable);
        myThread.start();
    }
}

public void stop() {
    synchronized (mStartStopLock) {
        myThread.interrupt();
        myThread.join();
    }
}

As you can see, despite the lock, if start() and stop() are called immediately one after another, nothing guarantees that interrupt() will have effect on the thread if it's a regular Java Thread. That's important because the Runnable uses the interrupted status for stopping itself.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Could you include a use case to show this API in action? \$\endgroup\$ – dfhwze Sep 16 '19 at 16:17
  • \$\begingroup\$ Thanks, I edited the question. \$\endgroup\$ – Jim Sep 16 '19 at 16:31
  • 2
    \$\begingroup\$ I posted this as a comment below but I think it might be worth mentioning here: Alternately, use an ExecutorService and get a Future when submitting jobs. You can .cancel a Future before it starts. \$\endgroup\$ – markspace Sep 17 '19 at 0:46
  • 2
    \$\begingroup\$ @markspace I think this comment should be promoted to an answer instead, since OP does not want to reinvent the wheel. \$\endgroup\$ – dfhwze Sep 17 '19 at 4:32
5
\$\begingroup\$

Review

  • A Java Thread's default behavior is:

    A thread interruption ignored because a thread was not alive at the time of the interrupt will be reflected by this method returning false.

  • Changing the default behavior of interruption in a thread's lifecycle seems a bit tricky to me. I don't think you should override exising methods to change their specification when dealing with such common and wellknown threading constructs.
  • Instead, I would create 2 new methods, requestRun() and requestInterruption() to clearly indicate different functionality from run() and interrupt().
  • If you do decide to override run() and interrupt() make sure to clearly state in the Javadoc that the specification of these methods has changed.
  • InterruptableThread is an unfortunate name, since Thread is also interruptible. A better name would be PreemptiveInterruptibleThread.
  • Since you have atomic booleans, perhaps you could check the value before taking the lock, in order to mitigate access to the locks. There is a possible race condition though. You can read more about Double-checked locking here. Is your question implementation Idempotent?
  • If the atomic booleans are only used inside this lock, they could be replaced with ordinary booleans.
  • I like the fact everything that could be made final, has been declared final.
  • I do believe there still is a race condition after releasing the lock in run() and before calling super.run().
\$\endgroup\$
2
  • 3
    \$\begingroup\$ Alternately, use an ExecutorService and get a Future when submitting jobs. You can .cancel a Future before it starts. \$\endgroup\$ – markspace Sep 17 '19 at 0:30
  • 1
    \$\begingroup\$ Only requestInterruption() is required, overriding requestRun() the way I did does not change the thread's specification. Moreover starting/stopping the library is not critical performance-wise, so the atomic booleans can be replaced and the lock kept for simplicity. Finally there is no race condition because the thread is alive as soon as the inherited run() is invoked, hence the thread is marked as interrupted in any case, which is what we want. I agree with all the rest, thanks for your answer. \$\endgroup\$ – Jim Sep 20 '19 at 14:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.