Как правильно остановить поток в 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 234

8 ответов:

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

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

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

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 попадает в возвращаемую подпрограмму.
  • метод Run завершается и возвращает неявно.

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

  • вызов system.exit (это убивает весь процесс)
  • вызовите объект потока 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 секунд (из-за вашего сна). Вы все еще можете вызвать поток.прерывание () если это действительно срочно - но предпочтительный способ всегда должен проверять флаг.

чтобы избежать ожидания в течение 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 что помогает потокам ждать завершения процесса. В этом случае рабочий класс настраивается с помощью 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;
        }

    }
}

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