Interrupting looped threads in Java
Asked Answered
A

4

6

I'm trying to understand how threads work in Java and currently investigating how to implement looped threads that can be cancelled. Here's the code:

public static void main(String[] args) throws Exception {
    Thread t = new Thread() {
        @Override
        public void run() {
            System.out.println("THREAD: started");
            try {
                while(!isInterrupted()) {
                    System.out.printf("THREAD: working...\n");
                    Thread.sleep(100);
                }
            } catch(InterruptedException e) {
                // we're interrupted on Thread.sleep(), ok

                // EDIT
                interrupt();

            } finally {
                // we've either finished normally
                // or got an InterruptedException on call to Thread.sleep()
                // or finished because of isInterrupted() flag

                // clean-up and we're done
                System.out.println("THREAD: done");
            }               
        }
    };

    t.start();
    Thread.sleep(500);
    System.out.println("CALLER: asking to stop");
    t.interrupt();
    t.join();
    System.out.println("CALLER: thread finished");
}

The thread I create is indended to be interrupted sooner or later. So, I check isInterrupted() flag to decide whether I need to go on and also catch InterruptedException to handle cases when I'm in a kind of waiting operation (sleep, join, wait).

Things I'd like to clarify are:

  1. Is it fine to use interruption mechanism for this kind of task? (comparing to having volatile boolean shouldStop)
  2. Is this solution correct?
  3. Is it normal that I swallow InterruptedException? I'm not really interested what was the piece of code where someone asked my thread to interrupt.
  4. Are there any shorter ways to solve this problem? (the main point is having 'infinite' loop)

EDIT Added call to interrupt() in catch for InterruptedException.

Alliance answered 11/8, 2011 at 11:40 Comment(1)
It's OK, but better not extend Thread since not extending its functionality. Implement a Runnable to be used by a standard Thread.Crier
F
4

I am answering no. 3:

Basically the question is: What purpose does an Interrupted exception have? It tells you to stop blocking (e.g. sleeping) and return early.

There are two ways dealing with an InterruptedException:

  • Rethrow it, so the thread remains interrupted
  • set Thread.currentThread.interrupt() again and do your cleanup work. This way you can be sure that another method in your thread starting to sleep will throw again

Simply swallowing an InterruptedException is not a good idea regarding the purpose of such an interrupt which is to finally terminate. But you are only asked to interrupt so you still have time to clean up.

In this case this might be an 'overreaction' of myself, but typically such code is much more complicated and how do you know, that some follow-up-code in this Thread would not call a blocking method again?

EDIT

Otherwise I think what you're doing is fine. For me a bit surprising, though, because I never saw anyone in his own code actually doing it.

And interesting article explaining why can be found here: http://www.ibm.com/developerworks/java/library/j-jtp05236/index.html

Fricke answered 11/8, 2011 at 11:44 Comment(2)
Well, I believe that rethrowing is not a case, since I can't throw from thread's run(). Should I call interrupt()?Alliance
isInterrupted does not reset the interrupted flag. And how do you stop a thread waiting or sleeping for a veeeeery long time without interrupting it?Jecon
J
2
  1. Yes, it's fine. You should document how a Thread/Runnable must be stopped. You could add a dedicated stop method on your Runnable implementation that encapsulates the stopping mechanism. Either use interrupt, or use a dedicated boolean value, or both.
  2. Yes, except the good practice is to restore the interrupt status when catching InterruptedException: Thread.currentThread().interrupt();
  3. No, you should restore the interrupt status
  4. None that I'm aware of
Jecon answered 11/8, 2011 at 11:47 Comment(0)
A
1

1) The way in your example is preferable to using a volatile flag (which is redundant since you already have the interrupted flag), according to the Java Concurrency in Practice book. It is how InterruptedExceptions were intended to be used.

2) Yes

3) you can eat the exception as long as you restore the interrupt flag status. The exception doesn't represent an error so eating it doesn't lose any information, it is purely a means of transferring control. (Restoring the interrupt flag status is important for cases where you have nested control structures that each need to be informed that the thread is cancelling, for a simple example like yours it's good form but if it's missing it won't hurt anything.)

4) no

Antagonist answered 11/8, 2011 at 12:26 Comment(0)
C
1

It's fine to use Interruption, but use them well. You have to re-throw Thread.currentThread().interrupt() in your catch. Here is a piece of code showing why :

public class MyThread extends Thread {
    private static boolean correct = true;

    @Override
    public void run() {
        while (true) {
            // Do Something 1
            for (int i = 0; i < 10; i++) { // combined loop
                // Do Something 2
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {
                    if (correct)
                        Thread.currentThread().interrupt(); // reinterrupting
                    System.out.println("First Catch");
                    break; // for
                }
            }
            try {
                // Do Something 3
                System.out.print("before sleep, ");
                Thread.sleep(1000);
                System.out.print("After sleep, ");
            } catch (InterruptedException ex) {
                if (correct)
                    Thread.currentThread().interrupt();
                System.out.println("Second catch");
                break; // while
            }
        }
        System.out.println("Thread closing");
    }

    private static void test() throws InterruptedException {
        Thread t = new MyThread();
        t.start();
        Thread.sleep(2500);
        t.interrupt();
        t.join();
        System.out.println("End of Thread");
    }

    public static void main(String[] args)
            throws InterruptedException {
        test();
        correct = false; // test "bad" way
        test();
    }
}

Another thing is, Interruptions don't always work when waiting on InputStreams. You then can use (for some) InterruptedIOException, but it won't always work. To understand these cases, you might want to try this piece of code :

public class Mythread extends Thread {
    private InputStream in;

    public Mythread(InputStream in) {
        this.in = in;
    }

    @Override
    public void interrupt() {
        super.interrupt();
        try {
            in.close(); // Close stream if case interruption didn't work
        } catch (IOException e) {}
    }

    @Override
    public void run() {
        try {
            System.out.println("Before read");
            in.read();
            System.out.println("After read");
        } catch (InterruptedIOException e) { // Interruption correctly handled
            Thread.currentThread().interrupt();
            System.out.println("Interrupted with InterruptedIOException");
        } catch (IOException e) {
            if (!isInterrupted()) { // Exception not coming from Interruption
                e.printStackTrace();
            } else { // Thread interrupted but InterruptedIOException wasn't handled for this stream
                System.out.println("Interrupted");
            }
        }
    }

    public static void test1() // Test with socket
            throws IOException, InterruptedException {
        ServerSocket ss = new ServerSocket(4444);
        Socket socket = new Socket("localhost", 4444);
        Thread t = new Mythread(socket.getInputStream());
        t.start();
        Thread.sleep(1000);
        t.interrupt();
        t.join();
    }

    public static void test2() // Test with PipedOutputStream
            throws IOException, InterruptedException { 
        PipedInputStream in = new PipedInputStream(new PipedOutputStream());
        Thread t = new Mythread(in);
        t.start();
        Thread.sleep(1000);
        t.interrupt();
        t.join();
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        test1();
        test2();
    }
}
Calends answered 11/8, 2011 at 12:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.