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

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

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

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

8 ответов


на IndexProcessor class вам нужен способ установки флага, который сообщает потоку, что ему нужно будет завершить, подобно переменной 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.");
        }
    }
}

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

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

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

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

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


простой ответ: Вы можете остановить поток внутренне одним из двух распространенных способов:

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

вы также можете остановить потоки извне:

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

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

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


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

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

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

    }
}

когда вы хотите завершить выполнение другого потока, выполните обратный отсчет на 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.");
        } 
    }
}

Если мы используем JDK 1.0, мы можем вызвать устаревший метод потока 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 / многопоточности


некоторая дополнительная информация. И флаг, и прерывание предлагаются в документе 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();
 }

Я не получил прерывание для работы в 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;
 }