billie billie - 1 month ago 9
Java Question

Any caveats to a runnable storing a reference to its own running thread?

I have a long running Runnable object and I wanted to provide a more graceful interrupt mechanism than having to call interrupt on the thread the object is running on.

The before code:

public class MyRunnable implements Runnable {
public void run() {
while(!Thread.currentThread().isInterrupted()) {
//do stuff
}
}
}

public class MyClass {
public static void main(String[] args) {
Runnable myRunnable = new MyRunnable();
Thread t = new Thread(myRunnable, "myRunnableThread");
t.start();

//do stuff

t.interrupt();

//do stuff
}
}


And the new code:

public class MyRunnable implements Runnable {
private Thread myThread = null;
public void run() {
myThread = Thread.currentThread();
while(!myThread.isInterrupted()) {
//do stuff
}
}

public void shutdown() {
if (myThread != null) {
myThread.interrupt();
//do other shutdown stuff
}
}
}

public class MyClass {
public static void main(String[] args) {
Runnable myRunnable = new MyRunnable();
Thread t = new Thread(myRunnable, "myRunnableThread");
t.start();

//do stuff

myRunnable.shutdown();

//do stuff
}
}


My question is, are there possible side effects or unknowns that holding a reference to your own thread, and providing limited access to that thread through public methods (as above) could cause? This is assuming that no-one ever calls the
run()
method directly, that it is always started from a new thread.

And I'm aware that I could use a volatile or atomic Boolean in the
run()
and
shutdown()
methods for communicating intent to shutdown, I'm more interested in learning than a solution. But solutions are still welcome!

Answer

For me your first approach is much better as less error prone and more "standard". But actually what you try to implement already exists (which proves that it makes sense and that it is not a bad practice but it is not easy to make it properly), it is called FutureTask, instead of shutdown you have cancel(boolean mayInterruptIfRunning) with true as value of mayInterruptIfRunning if you want to interrupt the thread running the task, I quote the javadoc:

Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.

For example:

// Task that will only sleep for 1 sec and print a message on interrupted
FutureTask<Void> myRunnable = new FutureTask<>(
    new Callable<Void>() {
        @Override
        public Void call() throws Exception {
            try {
                System.out.println("Sleep");
                Thread.sleep(1_000L);
            } catch (InterruptedException e) {
                System.out.println("Interrupted !!!");
                throw e;
            }
            return null;
        }
    }
);
new Thread(myRunnable, "myRunnableThread").start();

// Wait long enough to make sure that myRunnableThread is sleeping 
Thread.sleep(500L);
// Cancel the task and interrupt myRunnableThread
myRunnable.cancel(true);

Output:

Sleep
Interrupted !!!