Прерывание зацикленных потоков в Java

Я пытаюсь понять, как работают потоки в Java, и в настоящее время изучаю, как реализовать зацикленные потоки, которые можно отменить. Вот код:

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");
}

Поток, который я создаю, предназначен для того, чтобы его рано или поздно прервали. Итак, я проверяю флаг isInterrupted(), чтобы решить, нужно ли мне продолжать, а также перехватываю InterruptedException для обработки случаев, когда я нахожусь в операции ожидания (sleep, join, wait).

Вещи, которые я хотел бы уточнить:

  1. Можно ли использовать механизм прерывания для такого рода задач? (по сравнению с volatile boolean shouldStop)
  2. Правильно ли это решение?
  3. Это нормально, что я проглатываю InterruptedException? Меня не очень интересует, что это был за фрагмент кода, где кто-то просил прервать мой поток.
  4. Есть ли более короткие пути решения этой проблемы? (главное - "бесконечный" цикл)

EDIT Добавлен вызов interrupt() в catch для InterruptedException.


person Andrey Agibalov    schedule 11.08.2011    source источник
comment
Это нормально, но лучше не расширять Thread, так как не расширяет его функциональность. Реализуйте Runnable для использования стандартным потоком.   -  person user85421    schedule 11.08.2011


Ответы (4)


Я отвечаю нет. 3:

В основном возникает вопрос: какова цель прерываемого исключения? Он говорит вам прекратить блокировать (например, спать) и вернуться раньше.

Есть два способа справиться с InterruptedException:

  • Перебросьте его, чтобы поток остался прерванным
  • снова установите Thread.currentThread.interrupt() и сделайте свою работу по очистке. Таким образом, вы можете быть уверены, что другой метод в вашем потоке, начинающем спать, снова вызовет бросок

Простое проглатывание InterruptedException не является хорошей идеей с точки зрения цели такого прерывания, которое должно окончательно завершиться. Но вас просят только прервать, чтобы у вас еще было время навести порядок.

В этом случае это может быть моей «чрезмерной реакцией», но обычно такой код намного сложнее, и откуда вы знаете, что какой-то последующий код в этом потоке не вызовет снова блокирующий метод?

ИЗМЕНИТЬ

В противном случае я думаю, что то, что вы делаете, в порядке. Для меня это немного удивительно, потому что я никогда не видел, чтобы кто-то в своем собственном коде действительно делал это.

И интересную статью, объясняющую почему, можно найти здесь: http://www.ibm.com/developerworks/java/library/j-jtp05236/index.html

person Stefan Schubert-Peters    schedule 11.08.2011
comment
Ну, я считаю, что перекинуть не дело, так как я не могу кинуть из потока run(). Мне позвонить interrupt()? - person Andrey Agibalov; 11.08.2011
comment
isInterrupted не сбрасывает флаг прерывания. И как остановить поток, ожидающий или спящий очень долгое время, не прерывая его? - person JB Nizet; 11.08.2011

  1. Да, все в порядке. Вы должны задокументировать, как должен быть остановлен Thread/Runnable. Вы можете добавить в свою реализацию Runnable специальный метод остановки, который инкапсулирует механизм остановки. Либо используйте прерывание, либо используйте выделенное логическое значение, либо и то, и другое.
  2. Да, за исключением того, что хорошей практикой является восстановление статуса прерывания при перехвате InterruptedException: Thread.currentThread().interrupt();
  3. Нет, вы должны восстановить статус прерывания
  4. Нет, о чем я знаю
person JB Nizet    schedule 11.08.2011

1) Способ в вашем примере предпочтительнее использования изменчивого флага (который является избыточным, поскольку у вас уже есть прерванный флаг), согласно книге Java Concurrency in Practice. Именно так должны были использоваться InterruptedExceptions.

2) Да

3) вы можете съесть исключение, пока вы восстанавливаете статус флага прерывания. Исключение не представляет собой ошибку, поэтому его употребление не приводит к потере информации, это просто средство передачи управления. (Восстановление состояния флага прерывания важно для случаев, когда у вас есть вложенные управляющие структуры, каждая из которых должна быть проинформирована об отмене потока, для простого примера, такого как ваш, это хорошая форма, но если он отсутствует, это ничего не повредит.)

4) no

person Nathan Hughes    schedule 11.08.2011

Прерывание — это хорошо, но используйте его правильно. Вы должны повторно бросить Thread.currentThread().interrupt() в свой улов. Вот фрагмент кода, показывающий, почему:

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();
    }
}

Другое дело, что Interruptions не всегда работает при ожидании InputStreams. Затем вы можете использовать (для некоторых) InterruptedIOException, но это не всегда будет работать. Чтобы понять эти случаи, вы можете попробовать этот фрагмент кода:

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();
    }
}
person Dalshim    schedule 11.08.2011