Jak poprawnie zatrzymać wątek w Javie?

Potrzebuję rozwiązania, aby poprawnie zatrzymać wątek w Javie.

Mam IndexProcessorklasę, która implementuje interfejs 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;
            }
        }

    }
}

I mam ServletContextListener klasę, która uruchamia i zatrzymuje wątek:

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

Ale kiedy wyłączam tomcat, dostaję wyjątek w mojej klasie 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)

Używam JDK 1.6. Więc pytanie brzmi:

Jak mogę zatrzymać wątek i nie rzucać żadnych WYJĄTKÓW?

P. S.{[20] } Nie chcę używać metody .stop(); ponieważ jest przestarzały.

Author: TryinHard, 2012-06-09

8 answers

W klasie IndexProcessor potrzebujesz sposobu ustawienia flagi, która informuje wątek, że będzie musiał się zakończyć, podobnie jak zmienna run, której użyłeś właśnie w class scope.

Gdy chcesz zatrzymać wątek, ustawiasz tę flagę i wywołujesz join() na wątku i czekasz na jego zakończenie.

Upewnij się, że flaga jest bezpieczna dla wątku za pomocą zmiennej zmiennej lub za pomocą metod getter i setter, które są zsynchronizowane ze zmienną używaną jako flaga.

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

    }
}

Następnie w 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.");
        }
    }
}
 150
Author: DrYap,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-09 14:31:10

Używanie Thread.interrupt() jest całkowicie akceptowalnym sposobem na to. W rzeczywistości jest to prawdopodobnie preferowane do flagi, jak sugerowano powyżej. Powodem jest to, że jeśli jesteś w przerwanym wywołaniu blokującym (jak Thread.sleep lub używasz Javy.NIO Channel operations), będziesz w stanie od razu się z nich wyrwać.

Jeśli używasz flagi, musisz poczekać na zakończenie operacji blokowania, a następnie możesz sprawdzić flagę. W niektórych przypadkach i tak musisz to zrobić, np. używając standardowego InputStream/OutputStream które nie są przerywane.

W takim przypadku, gdy wątek zostanie przerwany, nie spowoduje przerwania IO, jednak możesz to zrobić rutynowo w kodzie (i powinieneś to zrobić w strategicznych punktach, gdzie możesz bezpiecznie zatrzymać i oczyścić)

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

Jak mówiłem, główną zaletą Thread.interrupt() jest to, że możesz natychmiast przerwać przerwane połączenia, czego nie możesz zrobić z podejściem flagowym.

 265
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-03 10:40:43

Prosta odpowiedź: Możesz zatrzymać wątek wewnętrznie na jeden z dwóch powszechnych sposobów:

  • metoda run uderza w podprogram powrotny.
  • metoda Run kończy działanie i zwraca ją bezwarunkowo.

Możesz także zatrzymać wątki zewnętrznie:

  • wywołanie system.exit (to zabija cały proces)
  • wywołanie metody obiektu wątku interrupt() *
  • sprawdź, czy wątek ma zaimplementowaną metodę, która brzmi jakby działała (jak kill() lub stop())

*: Oczekuje się, że to ma zatrzymać wątek. Jednak to, co faktycznie robi wątek, gdy tak się dzieje, zależy całkowicie od tego, co napisał deweloper, gdy stworzył implementację wątku.

Często spotykanym wzorcem w implementacjach run method jest while(boolean){}, gdzie wartość logiczna jest zwykle czymś o nazwie isRunning, jest zmienną członkowską klasy thread, jest zmienna zmienna zmienna zmienna jest zmienna zmienna jest zmienna zmienna jest zmienna zmienna jest zmienna zmienna jest zmienna zmienna jest Podprogramy te są ładne, ponieważ pozwalają wątkowi zwolnić wszelkie zasoby, które posiada przed zakończeniem.

 20
Author: hamsterofdark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-19 08:56:25

Należy zawsze kończyć wątki, sprawdzając flagę w pętli run() (jeśli istnieje).

Twój wątek powinien wyglądać tak:

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

Następnie możesz zakończyć wątek wywołując thread.stopExecuting(). W ten sposób wątek jest zakończony czysto, ale trwa to do 15 sekund (ze względu na sen). Nadal możesz zadzwonić do thread.interrupt (), jeśli jest to naprawdę pilne - ale preferowanym sposobem zawsze powinno być sprawdzenie flagi.

Aby uniknąć oczekiwania na 15 sekund, możesz podzielić sen w następujący sposób:

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

            LOGGER.debug("Processing");
        } catch (InterruptedException e) {
        ...
 9
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-09 14:22:40

Do synchronizacji wątków wolę używać CountDownLatch, która pomaga wątkom czekać do zakończenia procesu. W tym przypadku Klasa worker jest skonfigurowana z instancją CountDownLatch z podaną liczbą. Wywołanie metody await będzie blokowane, dopóki bieżąca liczba nie osiągnie zera z powodu wywołania metody countDown lub osiągnięcia limitu czasu. Takie podejście pozwala na natychmiastowe przerwanie wątku bez konieczności oczekiwania na określony czas oczekiwania:

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

    }
}

Kiedy aby zakończyć wykonywanie drugiego wątku, wykonaj countDown na CountDownLatch i join wątku do głównego wątku:

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.");
        } 
    }
}
 5
Author: Eduardo Sanchez-Ros,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-08 09:54:48

Jeśli używamy JDK 1.0, możemy wywołać przestarzałą metodę wątku stop (), aby ją zakończyć. Użycie stop() jest niezwykle niebezpieczne, ponieważ zabije Twój wątek, nawet jeśli jest w środku czegoś ważnego. Nie ma sposobu, aby się chronić, więc jeśli zauważysz kod, który używa stop (), powinieneś marszczyć brwi.

Jak zamknąć wątek?

W Javie uruchamianie wątków jest łatwe, ale ich wyłączenie wymaga dużo uwagi i wysiłku.

Oto Jak to jest zaprojektowany w Javie. W każdym wątku java znajduje się flaga o nazwie Interrupt status flag, którą możemy ustawić z zewnątrz, tj. wątek nadrzędny lub główny. Wątek może to sprawdzić od czasu do czasu i wstrzymać jego wykonanie. Dobrowolnie..!! Oto jak:

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

Źródło: Jak zatrzymać wątek w Javie / wielowątkowość

 4
Author: riteeka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-28 06:07:00

Jakieś dodatkowe informacje. Zarówno flaga, jak i przerwanie są sugerowane w dokumencie 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();
    }
}

Dla wątku, który czeka przez długi czas (np. na wejście), użyj Thread.interrupt

public void stop() {
     Thread moribund = waiter;
      waiter = null;
      moribund.interrupt();
 }
 3
Author: Feng,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-11 21:03:06

Nie dostałem przerwania do pracy w Androidzie, więc użyłem tej metody, działa idealnie:

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;
 }
 2
Author: Mindborg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-07 10:42:29