Shutting down threads cleanly

来源:互联网 发布:南风知我意 七微txt 编辑:程序博客网 时间:2024/05/17 04:12


From: http://www.javaspecialists.eu/archive/Issue056.html

 

Shutting down threadscleanly

by Dr.Heinz M. Kabutz

 

Welcome to the 56th edition of The Java(tm) Specialists' Newsletter sentto 4609 Java Specialists in 85 countries.Whenever I think I wrote a "killer" newsletter, I get a lukewarmresponse from my readers, and when I think I wrote a flop, I get accolades,like with my last newsletter about Oak. I expected rotten tomatoes in the formof "here are 29 other newsletters about Oak and modern Java". PerhapsI should try and writeflops, then I will have more successes ;-)

When I was in Germany from June to August 2000, welived in a village about 45 minutes by train from Infor AG.Every day I had 1.5 hours to read books about various topics. Over the courseof 3 months, I got through a lot of reading, I even read The Java VirtualMachine Specification! Much like the Oak specification, it is a*must* for any serious Java developer to read. The more I read in that spec,the more I realised that the hope of "write once, run anywhere" was awishful dream. Another book that I devoured is ConcurrentProgramming in Java, 2nd Edby Doug Lea, one of the writers of java.util.HashMap. Mythoughts on shutting down threads is based on ideas I gleaned from those twobooks, so before you shoot me down, please read the books. The Java 2Performance and Idiom Guide is also an excellent book to read forthe reason that it got me thinking about performance the way that no other bookhas. It is quite dated, which cannot be avoided with a book about a languagethat changes so quickly. That book also agrees with my approach of shuttingdown threads.

Upcoming Java MasterCourses:
    Duesseldorf, Germany, Aug 22
    Chania, Crete, Sep 6
    Cape Town, South Africa,Sep 12

In-house courses ifthese dates or locations do not suit you. Note that the course in Crete may also be attended remotely via webinar.

How to shutdown threads cleanly

I remember starting off with JDK 1.0, and playing withThreads. I would start a Thread, and then to stop it, I simply called stop(). It wasthe most obvious thing to do, with disastrous consequences. If we refer back tothe Oak Newsletter,that was the same as having all yourcode unprotected, and receiving an asynchronous exceptionin your Thread. The exception that is thrown asynchronously is java.lang.ThreadDeath. Needlessto say, the more I think about it, the more I am baffled that they allowed thatin the first place, especially in the absence of theprotect keywordthat they had in Oak. Using stop() isincredibly dangerous, as it will kill your thread even if it is in the middleof something important. There is no way to protect yourself, so if you spotcode that uses stop(), youshould frown.

So, how do you shutdown a thread cleanly? The developersof Java have actually left the protect mechanism in place for us to use, it isjust named differently. First of all, we should never use stop(). Ever.Period. Do not even think of using it. Sun should remove it from java.lang.Thread assoon as possible. Don't. No. Noooooo. Doooown. Secondly, the only place wherewe are allowed to receive an exception to tell us that the thread is beingshutdown is while the thread is blocked. Getting a shutdown notificationat any other time would bedangerous and nondeterministic. The way this works is via the java.lang.InterruptedException. I admitthatInterruptedException is not the mostobvious choice of name forindicating that another thread is trying to shutdown your thread.

I have seen code that uses a boolean flag to indicatewhether the thread is running or not. However, Java already provides that flagin the form of the interrupted flag, so why duplicate effort? Usually, the codewould work something like this:

public class UsingFlagToShutdownThread extends Thread {
  private volatile boolean running = true;
  public void run() {
    while (running) {
      System.out.print(".");
      System.out.flush();
      try {
        Thread.sleep(1000);
      } catch (InterruptedException ex) {}
    }
    System.out.println("Shutting down thread");
  }
  public void shutdown() {
    running = false;
  }
  public static void main(String[] args)
      throws InterruptedException {
    UsingFlagToShutdownThread t = new UsingFlagToShutdownThread();
    t.start();
    Thread.sleep(5000);
    t.shutdown();
  }
}

What is so bad with that code? This example is not toobad, since the longest we would wait unnecessarily would be one second. Howeverif we normally sleep for 30 seconds, then it could take a while before yourprogram is completely shut down. This is especially true if you have a lot ofthreads and you join() eachone to make sure that it does finish.

Java has another mechanism that you should rather use:simply interrupt the thread. The code would then look like this:

public class UsingInterruptToShutdownThread extends Thread {
  public void run() {
    while (true) {
      System.out.print(".");
      System.out.flush();
      try {
        Thread.sleep(1000);
      } catch (InterruptedException ex) {
        Thread.currentThread().interrupt(); // very important
        break;
      }
    }
    System.out.println("Shutting down thread");
  }
  public static void main(String[] args)
      throws InterruptedException {
    Thread t = new UsingInterruptToShutdownThread();
    t.start();
    Thread.sleep(5000);
    t.interrupt();
  }
}

I must admit that I have not seen many programmers handleInterruptedExceptions correctly, i.e. using my way ;-) Most of the time,programmers view InterruptedException as an irritating checked exception thatthey have to catch, but which they usually ignore:

while (true) {
  // ... do something
  try {
    Thread.sleep(30000);
  } catch (InterruptedException ex) {}
}

Why do we have to interrupt the thread again?

In my example, after I caught the InterruptedException, Iused Thread.currentThread().interrupt() toimmediately interrupted the thread again. Why is this necessary? When theexception is thrown, the interrupted flag is cleared, so if you have nestedloops, you will cause trouble in the outer loops. Consider the following code:

public class NestedLoops extends Thread {
  private static boolean correct = true;
  public void run() {
    while (true) {
      System.out.print(".");
      System.out.flush();
      for (int i = 0; i < 10; i++) {
        System.out.print("#");
        System.out.flush();
        try {
          Thread.sleep(100);
        } catch (InterruptedException ex) {
          if (correct) Thread.currentThread().interrupt();
          System.out.println();
          System.out.println("Shut down inner loop");
          break;
        }
      }
      try {
        Thread.sleep(1000);
      } catch (InterruptedException ex) {
        if (correct) Thread.currentThread().interrupt();
        System.out.println();
        System.out.println("Shut down outer loop");
        break;
      }
    }
    System.out.println("Shutting down thread");
  }
  private static void test() throws InterruptedException {
    Thread t = new NestedLoops();
    t.start();
    Thread.sleep(6500);
    t.interrupt();
    t.join();
    System.out.println("Shutdown the thread correctly");
  }
  public static void main(String[] args)
      throws InterruptedException {
    test();
    correct = false;
    test();
  }
}

When you run this code, you will see something like this:

.##########.##########.##########.######
Shut down inner loop
 
Shut down outer loop
Shutting down thread
Shutdown the thread correctly
.##########.##########.##########.######
Shut down inner loop
.##########.##########.##########.##########.##########.  etc.

Herein lies the danger with this approach: if some libraryincorrectly handles InterruptedException thenyour code will not shut down correctly.

From a purely theoretical view, you should use theinterrupt mechanism of threads to shut them down. However, you have to be verycareful that you use that mechanism throughout your code, otherwise you willnot be able to shut down all your threads.

What about threads blocked on IO?

Threads can be blocked on wait()sleep(), waitingto enter a synchronized block or waiting on some IO to complete. We cannot shutdown a thread waiting to enter a synchronized block, so if you have a livelockor deadlock you will not be able to shut down your system cleanly. wait() and sleep() boththrow an InterruptedException, as does join(). But, whatabout when you're blocked on IO? There is an exception calledjava.io.InterruptedIOException, which issupposed to cover the situation where you interrupt a thread that is waiting onsome IO to complete. As you might have guessed, it is not implementedconsistently. It works for piped streams, but none of the others seem to havethat effect.

If you want to stop a thread waiting on a socket, you willhave to unfortunately close the socket underneath the thread. Fortunately,the interrupt() method is not final, so youcan override it to also close the socket. Inside the catch clause of java.io.IOException youcan then check whether the thread has been interrupted or not:

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
 
public class BlockedOnIO extends Thread {
  private final InputStream in;
  public BlockedOnIO(InputStream in) {
    this.in = in;
  }
  public void interrupt() {
    super.interrupt();
    try {
      in.close();
    } catch (IOException e) {} // quietly close
  }
  public void run() {
    try {
      System.out.println("Reading from input stream");
      in.read();      
      System.out.println("Finished reading");
    } catch (InterruptedIOException e) {
      Thread.currentThread().interrupt();
      System.out.println("Interrupted via InterruptedIOException");
    } catch (IOException e) {
      if (!isInterrupted()) {
        e.printStackTrace();
      } else {
        System.out.println("Interrupted");
      }
    }
    System.out.println("Shutting down thread");
  }
}

For shutting down threads reading from sockets, we woulddo something like this:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
 
public class BlockedOnSocketIO {
  public static void main(String[] args)
      throws IOException, InterruptedException {
    ServerSocket ss = new ServerSocket(4444);
    Socket socket = new Socket("localhost", 4444);
    System.out.println("Made socket, now reading from socket");
    Thread t = new BlockedOnIO(socket.getInputStream());
    t.start();
    Thread.sleep(5000);
    t.interrupt();
  }
}

When we run our code, we see the following:

Made socket, now reading from socket
Reading from input stream
Interrupted
Shutting down thread

Alternatively, when we use Pipes:

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
 
public class BlockedOnPipedIO {
  public static void main(String[] args)
      throws IOException, InterruptedException {
    PipedInputStream in =
      new PipedInputStream(new PipedOutputStream());
    Thread t = new BlockedOnIO(in);
    t.start();
    Thread.sleep(5000);
    t.interrupt();
  }
}

When we run that code, we see the following:

Reading from input stream
Interrupted via InterruptedIOException
Shutting down thread

Unfortunately, the IO library in Java is not consistent,so you have to cater for both possibilities in your shutdown methods.

I hope that this newsletter will be as useful to you as ithas been to me. Shutting down threads cleanly is unfortunately not as easy asit should be, but the mechanism in this newsletter is superior to calling stop() (andthereby using an asynchronous exception)and it is also better than using a flag to indicate whether the thread issupposed to carry on running or not.

The only problem with my approach is that if you use somelibrary that does not handle InterruptedException correctly, you will haveproblems shutting down your thread. You might have to have a separate threadthat callsjoin() with a timeoutand repeatedly interrupts the thread until it is shut down.

That's the end of the newsletter. The birds are singing tocelebrate spring, my baby sister is here to visit, so we are now going tocelebrate life with a Cuban cigar :-)

Heinz

 


原创粉丝点击