Как правильно остановить поток в Java?


276

Мне нужно решение, чтобы правильно остановить поток в Java.

У меня есть IndexProcessorкласс, который реализует интерфейс Runnable:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    @Override
    public void run() {
        boolean run = true;
        while (run) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                run = false;
            }
        }

    }
}

И у меня есть ServletContextListenerкласс, который запускает и останавливает поток:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        thread = new Thread(new IndexProcessor());
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (thread != null) {
            thread.interrupt();
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}

Но когда я закрываю tomcat, я получаю исключение в своем классе IndexProcessor:

2012-06-09 17:04:50,671 [Thread-3] ERROR  IndexProcessor Exception
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at lt.ccl.searchengine.processor.IndexProcessor.run(IndexProcessor.java:22)
    at java.lang.Thread.run(Unknown Source)

Я использую JDK 1.6. Итак, вопрос:

Как я могу остановить поток и не выбрасывать исключения?

PS Я не хочу использовать .stop();метод, потому что он устарел.


1
Завершение потока на полпути всегда будет генерировать исключение. Если это нормальное поведение, то вы можете просто поймать и игнорировать InterruptedException. Это то, что я думаю, но мне также интересно, как стандартным образом.
nhahtdh

Я не использовал потоки очень часто, поэтому я довольно новичок в потоках, поэтому я не знаю, нормально ли это игнорировать исключение. Вот почему я спрашиваю.
Паулюс Матулионис

Во многих случаях нормальным поведением является игнорирование исключения и прекращение обработки метода. Посмотрите мой ответ ниже, почему это лучше, чем подход, основанный на флаге.
Мэтт

1
Аккуратное объяснение Б. Гетца относительно этого InterruptedExceptionможно найти по адресу ibm.com/developerworks/library/j-jtp05236 .
Даниэль

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

Ответы:


173

В IndexProcessorклассе вам нужен способ установки флага, который информирует поток о том, что он должен завершиться, подобно переменной, runкоторую вы использовали только в области видимости класса.

Когда вы хотите остановить поток, вы устанавливаете этот флаг и вызываете join()поток и ждете его завершения.

Убедитесь, что флаг является потокобезопасным, используя переменную volatile или методы getter и setter, которые синхронизируются с переменной, используемой в качестве флага.

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean running = true;

    public void terminate() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                running = false;
            }
        }

    }
}

Тогда в SearchEngineContextListener:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        runnable = new IndexProcessor();
        thread = new Thread(runnable);
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (thread != null) {
            runnable.terminate();
            thread.join();
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}

3
Я сделал точно так же, как вы привели примеры в своем ответе, перед тем как я посмотрел, что вы его редактировали. Отличный ответ! Спасибо, теперь все работает отлично :)
Paulius Matulionis

1
Что если логика потока сложна и вызывает много методов других классов? Невозможно проверить логический флаг везде. Что делать то?
Сотерик

Вам придется изменить дизайн кода так, чтобы он был построен таким образом, чтобы сигнал к Runnable приводил к завершению потока. В большинстве случаев этот цикл используется в методе run, поэтому обычно проблема не возникает.
DrYap

3
Что происходит, если оператор join () генерирует исключение InterruptedException?
benzaita

14
Понравился за распространение плохих советов. Подход с ручным переключением флагов означает, что приложение должно ждать завершения сна, где прерывание прерывает сон. Было бы легко изменить это, чтобы использовать прерывание Thread #.
Натан Хьюз

298

Использование Thread.interrupt()- вполне приемлемый способ сделать это. На самом деле, это, вероятно, предпочтительнее флага, как предложено выше. Причина в том, что если вы находитесь в режиме прерывистого блокирующего вызова (например, Thread.sleepили используете операции канала java.nio), вы действительно сможете выйти из этого сразу.

Если вы используете флаг, вам нужно дождаться завершения операции блокировки, а затем вы можете проверить свой флаг. В некоторых случаях вы должны сделать это в любом случае, например, используя стандартные InputStream/ OutputStreamкоторые не прерываются.

В этом случае, когда поток прерывается, он не прерывает ввод-вывод, однако вы можете легко сделать это регулярно в своем коде (и вы должны делать это в стратегических точках, где вы можете безопасно остановить и очистить)

if (Thread.currentThread().isInterrupted()) {
  // cleanup and stop execution
  // for example a break in a loop
}

Как я уже сказал, главное преимущество Thread.interrupt()заключается в том, что вы можете немедленно прервать прерывистые вызовы, чего нельзя сделать с помощью флагового подхода.


32
+1 - Thread.interupt () определенно предпочтительнее реализации той же вещи с использованием специального флага.
Стивен К

2
Я также считаю, что это идеальный и эффективный способ сделать это. +1
RoboAlex

4
В коде есть небольшая опечатка, у Thread.currentThread () нет круглых скобок.
Влад V

1
На самом деле использование флага не является предпочтительным, потому что кто-то еще, вступивший в контакт с потоком, может прерывать его из другого места, вызывая его остановку и затрудняя отладку. Всегда используйте флаг.
JohnyTex

В этом конкретном случае вызов interrupt()может быть в порядке, но во многих других случаях это не так (например, если ресурс должен быть закрыт). Если кто-то изменит внутреннюю работу цикла, вам нужно помнить, чтобы перейти interrupt()к логическому способу. Я бы пошел с безопасного пути с самого начала и использовал флаг.
m0skit0

25

Простой ответ: Вы можете остановить поток ВНУТРЕННЕ одним из двух распространенных способов:

  • Метод run выполняет возврат в подпрограмму.
  • Метод run завершается и возвращается неявно.

Вы также можете остановить темы ВНЕШНЕЕ:

  • Звоните system.exit(это убивает весь ваш процесс)
  • Вызвать метод объекта потока interrupt()*
  • Посмотрите, есть ли в потоке реализованный метод, который звучит так, как будто он будет работать (например, kill()или stop())

*: Ожидается, что это должно остановить поток. Однако то, что на самом деле делает поток, когда это происходит, полностью зависит от того, что написал разработчик, когда создавал реализацию потока.

Обычный шаблон, который вы видите в реализациях метода run, - это while(boolean){}где логическое значение обычно называется именем isRunning, оно является переменной-членом своего класса потока, оно изменчиво и обычно доступно другим потокам с помощью метода установки типа, например kill() { isRunnable=false; }. Эти подпрограммы хороши тем, что позволяют потоку высвобождать любые ресурсы, которые он держит, перед завершением.


3
«Эти подпрограммы хороши тем, что позволяют потоку высвободить любые ресурсы, которые он держит, перед завершением». Я не понимаю Вы можете прекрасно очистить удерживаемые ресурсы потока, используя «официальный» статус прерывания. Просто проверьте его с помощью Thread.currentThread (). IsInterrupted () или Thread.interrupted () (в зависимости от того, что вам нужно), или перехватите InterruptedException и выполните очистку. Где проблема?
Франц Д.

Я не смог понять, почему метод flag работает, потому что я не понял, что он останавливается, когда возвращаются результаты выполнения !!! Это было так просто, дорогой сэр, спасибо, что указали на это, никто не сделал этого явно.
thahgr

9

Вы должны всегда заканчивать потоки, проверяя флаг в run()цикле (если есть).

Ваша тема должна выглядеть так:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean execute;

    @Override
    public void run() {
        this.execute = true;
        while (this.execute) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                this.execute = false;
            }
        }
    }

    public void stopExecuting() {
        this.execute = false;
    }
}

Затем вы можете закончить тему, позвонив thread.stopExecuting(). Таким образом, поток заканчивается чистым, но это занимает до 15 секунд (из-за вашего сна). Вы все еще можете вызвать thread.interrupt (), если это действительно срочно, но предпочтительным способом всегда должна быть проверка флага.

Чтобы избежать ожидания в течение 15 секунд, вы можете разделить сон следующим образом:

        ...
        try {
            LOGGER.debug("Sleeping...");
            for (int i = 0; (i < 150) && this.execute; i++) {
                Thread.sleep((long) 100);
            }

            LOGGER.debug("Processing");
        } catch (InterruptedException e) {
        ...

2
это не Thread- он реализует Runnable- вы не можете вызывать Threadметоды для него, пока вы не объявите его как, и Threadв этом случае вы не можете вызватьstopExecuting()
Дон Чидл

7

Как правило, поток прерывается, когда он прерывается. Итак, почему бы не использовать родной логическое значение? Попробуйте isInterrupted ():

Thread t = new Thread(new Runnable(){
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                // do stuff         
            }   
        }});
    t.start();

    // Sleep a second, and then interrupt
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {}
    t.interrupt();

ref- Как я могу убить нить? без использования stop ();


5

Для синхронизации потоков я предпочитаю использовать, CountDownLatchкоторый помогает потокам ждать завершения процесса. В этом случае рабочий класс настроен с CountDownLatchэкземпляром с заданным количеством. Вызов awaitметода блокируется до тех пор, пока текущий счетчик не достигнет нуля из-за вызовов countDownметода или заданного времени ожидания. Этот подход позволяет мгновенно прерывать поток, не дожидаясь истечения указанного времени ожидания:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    private final CountDownLatch countdownlatch;
    public IndexProcessor(CountDownLatch countdownlatch) {
        this.countdownlatch = countdownlatch;
    }


    public void run() {
        try {
            while (!countdownlatch.await(15000, TimeUnit.MILLISECONDS)) {
                LOGGER.debug("Processing...");
            }
        } catch (InterruptedException e) {
            LOGGER.error("Exception", e);
            run = false;
        }

    }
}

Если вы хотите , чтобы закончить выполнение другого потока, выполнить CountDown на CountDownLatchи joinнить к главной теме:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;
    private CountDownLatch countdownLatch = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        countdownLatch = new CountDownLatch(1);
        Thread thread = new Thread(new IndexProcessor(countdownLatch));
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (countdownLatch != null) 
        {
            countdownLatch.countDown();
        } 
        if (thread != null) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
            }
            LOGGER.debug("Thread successfully stopped.");
        } 
    }
}

3

Некоторая дополнительная информация. И флаг, и прерывание предлагаются в документе Java.

https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html

private volatile Thread blinker;

public void stop() {
    blinker = null;
}

public void run() {
    Thread thisThread = Thread.currentThread();
    while (blinker == thisThread) {
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e){
        }
        repaint();
    }
}

Для потока, который ждет длительные периоды (например, для ввода), используйте Thread.interrupt

public void stop() {
     Thread moribund = waiter;
      waiter = null;
      moribund.interrupt();
 }

3
Никогда не игнорируйте InterruptedException. Это означает, что какой-то другой код явно просит ваш поток прекратить. Поток, который игнорирует этот запрос, является мошенническим потоком. Правильный способ обработки InterruptedException - выйти из цикла.
VGR

2

Я не получил прерывание для работы в Android, поэтому я использовал этот метод, работает отлично:

boolean shouldCheckUpdates = true;

private void startupCheckForUpdatesEveryFewSeconds() {
    threadCheckChat = new Thread(new CheckUpdates());
    threadCheckChat.start();
}

private class CheckUpdates implements Runnable{
    public void run() {
        while (shouldCheckUpdates){
            System.out.println("Do your thing here");
        }
    }
}

 public void stop(){
        shouldCheckUpdates = false;
 }

Это может привести к сбою, так как shouldCheckUpdates - нет volatile. См. Docs.oracle.com/javase/specs/jls/se9/html/jls-17.html#jls-17.3 .
VGR

0

Когда-нибудь я попробую 1000 раз в моем onDestroy () / contextDestroyed ()

      @Override
    protected void onDestroy() {
        boolean retry = true;
        int counter = 0;
        while(retry && counter<1000)
        {
            counter++;
            try{thread.setRunnung(false);
                thread.join();
                retry = false;
                thread = null; //garbage can coll
            }catch(InterruptedException e){e.printStackTrace();}
        }

    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.