RyanFX's Blog: How to stop a thread in Java

Monday, July 1, 2013

How to stop a thread in Java

Stopping threads in Java are one of those things in life that are extremely easy in practice yet conceptually unintuitive.  There are also several ways of doing so which adds to the confusion.  This article outlines the three major patterns.


Additional resources

If you are looking for further reading, I highly recommend Java Concurrency in Practice.  It is a fantastic book that highlights many common developer pitfalls and demonstrates great, reusable patterns for multithreaded applications.



First we'll cover the absolute WRONG way that you should NEVER use!

package com.blogspot.ryanfx;

public class WrongRunnable implements Runnable{

  private static long SLEEP = 5000;
 
  @Override
  public void run() {  
    for (long count = 0; count < SLEEP; count++){
      System.out.println("Waiting... " + count);
    }
  }
  public static void main(String[] args) throws InterruptedException{
    Thread myThread = new Thread(new WrongRunnable());
    myThread.start();
    myThread.sleep(1);
    myThread.stop();
  }
}

In the above example a new Thread is instantiated with our Runnable.  This Thread is then started which begins execution of whatever is inside the overridden run() method.  The main thread is put to sleep for 1 millisecond and then the new thread is stopped.  While this looks perfectly reasonable and acceptable by all stretches of the imagination, it's actually a terrible, terrible thing to do.  Thread#stop() is a deprecated method and for very good reasons.  It's analogous to kill -9 in the sense that it doesn't do a very good job with cleaning up after itself.  It can leave your program in an extremely inconsistent state and and wreck havoc on locks or mutexes.  Oracle (Sun then) even says not to use it!

You may be asking yourself "Well if we can't stop() the thread, how are we going to stop the thread!?"

Long story short, you return.  Below is an example of such a pattern.


package com.blogspot.ryanfx;

public class FlagRunnable implements Runnable{

 private volatile boolean exit = false;
 private static long SLEEP = 5000;
 
 @Override
 public void run() {
  
  for (long count = 0; count < SLEEP; count++){
   System.out.println("Waiting... " + SLEEP);
   if (exit == true){
    return;
   }
  }
 }
 
 public void requestExit(){
  exit = true;
 }
 
 public static void main(String[] args) throws InterruptedException{
  FlagRunnable myRunnable = new FlagRunnable();
  Thread myThread = new Thread(myRunnable);
  myThread.start();
  myThread.sleep(1);
  myRunnable.requestExit();
 }
}

In the above example we again create a Runnable and a new Thread. What's different is that we have introduced a new variable called exit. Every iteration of the loop inside run(), exit is evaluated. If exit is ever true, we return which signals to the JVM that this Thread has now completed. The JVM begins cleaning up any loose structures and it properly stops execution of the Thread. You may have noticed that boolean is declared volatile. What this does is give a hint to the compiler that certain optimizations cannot be taken because this variable is modified by external threads. If this were not used the value of exit could be cached and reused for some time before its changed state is detected.  Once the main thread has slept for 1 millisecond it calls the requestExit() method in our Runnable to signal a shutdown.  The very next time the loop in run() iterates, it returns.  While this is a perfectly acceptable pattern there are often times where this shouldn't be used.  Imagine instead of calling System.out we were instead calling a very long running method such as network IO.  When requestExit() is invoked, we are guaranteed to not iterate more times in the loop, but it may take quite some time before the Thread actually finishes.  Additionally, adding in exit flags  exit conditionals becomes repetitive in all of your threaded classes.

package com.blogspot.ryanfx;

public class NativeRunnable implements Runnable{

 private static long SLEEP = 5000;
 
 @Override
 public void run() {
  
  for (long count = 0; count < SLEEP; count++){
   System.out.println("Waiting... " + SLEEP);
   if (Thread.isInterrupted()){
    return;
   }
  }
 }

 
 public void requestExit(){
  exit = true;
 }
 
 public static void main(String[] args) throws InterruptedException{
  Thread myThread = new Thread(new WrongRunnable());
  myThread.start();
  myThread.sleep(1);
  myThread.interrupt();
 }
}

This last version uses functionality built into java called Interrupts.  A Thread can signal to a Thread (another one, or itself) that it should begin termination with the call interrupt().  In this example, every iteration of the loop performs a test to see if it has been interrupted.  If it has been interrupted, the thread calls return and the JVM cleans it up.  You may notice that some commonly called methods such as Thread#sleep() and Process#waitFor() declare that they throw InterruptedException.  This means they support being stopped with the interrupt() method call and you can terminate their execution early.  An important note is that we have not added any additional flags or methods to our Runnable.  No additional work is required on our part!  Another advantage to using this method is more timely termination of your threads.  Imagine instead of System.out.println("Waiting... " + SLEEP); we did MyDownloader.download(somehugefile.zip).  Using the flag method instead of this method would not support interrupting the download, however it would prevent it from downloading again the next iteration of the for-loop.

Beware of one catch with the above method!

When isInterrupted() is called the interrupted flag on the Thread is reset.  This means if you interrupt() a Thread and then then do not take action, the next call to isInterrupted will return false (unless interrupt() has been called yet again)



1 comment:

Unknown said...

"When isInterrupted() is called the interrupted flag on the Thread is reset."

Have you noticed the difference between "isInterrupted()" (which does not reset the flag) and "interrupted()"?