Вопрос по java, listener, multithreading – Как правильно остановить поток в Java?

245

Мне нужно решение, чтобы правильно остановить поток в 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. Итак, вопрос:

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

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

Аккуратное объяснение Б. Гетца относительноInterruptedException можно найти наibm.com/developerworks/library/j-jtp05236. Daniel
Завершение потока на полпути всегда будет генерировать исключение. Если это нормальное поведение, то вы можете просто поймать и игнорироватьInterruptedException, Это то, что я думаю, но мне также интересно, как стандартным образом. nhahtdh
Как насчет использованияjoin()? Havelock
Во многих случаях нормальным поведением является игнорирование исключения и прекращение обработки метода. Посмотрите мой ответ ниже, почему это лучше, чем подход, основанный на флаге. Matt
Я не часто использую потоки, поэтому я довольно новичок в потоках, поэтому я не знаю, нормально ли это игнорировать исключение. Вот почему я спрашиваю. Paulius Matulionis

Ваш Ответ

9   ответов
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();
 }
Никогда не игнорируйте InterruptedException. Это означает, что какой-то другой код явно просит ваш поток прекратить. Поток, который игнорирует этот запрос, является мошенническим потоком. Правильный способ обработки InterruptedException - выйти из цикла.
1

когда он прерывается. Итак, почему бы не использовать родной логическое значение? Попробуйте 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 ();

23

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

The run method hits a return subroutine. Run method finishes, and returns implicitly.

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

Call system.exit (this kills your entire process) Call the thread object's interrupt() method * See if the thread has an implemented method that sounds like it would work (like kill() or stop())

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

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

Я не смог понять, почему метод flag работает, потому что я не понял, что он останавливается, когда возвращаются результаты выполнения !!! Это было так просто, дорогой сэр, спасибо, что указали на это, никто не сделал этого явно.
& quot; Эти подпрограммы хороши тем, что позволяют потоку высвобождать любые ресурсы, которые он держит, перед завершением. & quot; Я не понимаю. Вы можете прекрасно очистить ресурсы, хранящиеся в потоке, используя "официальный" ресурс. прерванный статус. Просто проверьте его, используя Thread.currentThread (). IsInterrupted () или Thread.interrupted () (в зависимости от того, что вам нужно), или перехватите InterruptedException и выполните очистку. В чем проблема?
274

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() является то, что вы можете немедленно прервать прерывистые вызовы, что вы не можете сделать с подходом флага.

В коде есть небольшая опечатка, у Thread.currentThread () нет круглых скобок.
+1 - Thread.interupt () естьdefinitely предпочтительнее реализации той же вещи с использованием специального флага.
В этом конкретном случае вызоваinterrupt() может быть в порядке, но во многих других случаях это не так (например, если ресурс должен быть закрыт). Если кто-то изменит внутреннюю работу цикла, вы должны помнить, чтобы изменитьinterrupt() к логическому пути. Я бы пошел с безопасного пути с самого начала и использовал флаг.
Я также считаю, что это идеальный и эффективный способ сделать это. +1
На самом деле использование флага не является предпочтительным, потому что кто-то еще, вступающий в контакт с потоком, может прерывать его откуда-то еще, вызывая его остановку и будучи очень трудным для отладки. Всегда используйте флаг.
5

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

Как мы аккуратно закрываем поток?

В Java запуск потоков очень прост, но его закрытие требует много внимания и усилий.

Вот как это разработано в Java. В каждом потоке Java есть флаг, называемый флагом состояния прерывания, который мы можем установить снаружи, т.е. в родительском или основном потоке. И поток может время от времени проверять это и останавливает его выполнение. Добровольно..!! Вот как:

Thread loop = new Thread(new Runnable() {
@Override
public void run() {
  while (true) {
    if (Thread.interrupted()) {
      break;
    }
    // Continue to do nothing
  }
}}); loop.start(); loop.interrupt();

источник:Как остановить поток в Java | MultiThreading

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

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.");
        }
    }
}
Я сделал точно так же, как вы привели примеры в своем ответе, перед тем как я посмотрел, что вы его редактировали. Отличный ответ! Спасибо, теперь все работает отлично :) Paulius Matulionis
Вам придется изменить дизайн кода так, чтобы он был построен таким образом, чтобы сигнал к Runnable приводил к завершению потока. В большинстве случаев этот цикл используется в методе run, поэтому проблема обычно не возникает.
Что происходит, если оператор join () генерирует исключение InterruptedException?
Что если логика потока является сложной и вызывает много методов других классов? Невозможно проверить логический флаг везде. Что делать то?
Понравился за распространение плохих советов. Подход с ручным переключением флагов означает, что приложение должно ждать завершения сна, где прерывание прерывает сон. Было бы легко изменить это, чтобы использовать прерывание Thread #.
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) {
        ...
это неThread - это реализуетRunnable - Вы не можете позвонитьThread методы на нем, если вы не объявите это какThread в этом случае вы не можете позвонитьstopExecuting()
2

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

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.

Похожие вопросы