Як правильно зупинити тему на 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

Я не використовую нитки дуже часто, тому я досить новий в потоках, тому не знаю, чи нормальна поведінка ігнорувати виняток. Ось чому я прошу.
Paulius Matulionis

У багатьох випадках нормальна поведінка ігнорувати виняток та припиняти обробку методу. Дивіться мою відповідь нижче, чому це краще, ніж прапор, заснований на прапорі.
Метт

1
Точне пояснення Б. Геца щодо цього InterruptedExceptionможна знайти на веб-сайті ibm.com/developerworks/library/j-jtp05236 .
Даніель

InterruptException - це не проблема. Ваша єдина проблема в опублікованому коді - ви не повинні записувати це як помилку, насправді не існує вагомої причини записати його як усі, крім як налагодження, щоб продемонструвати це сталося у випадку, якщо вас цікавить . вибрана відповідь невдала, оскільки вона не дозволяє скорочувати короткі дзвінки на дзвінки, такі як сон і очікування.
Натан Х'юз

Відповіді:


173

Для IndexProcessorкласу вам потрібен спосіб встановити прапор, який інформує нитку про те, що його потрібно буде припинити, подібно до змінної, runяку ви використовували саме в області застосування класу.

Коли ви хочете зупинити потік, ви встановите цей прапор і зателефонуйте join()на потік і дочекайтеся його закінчення.

Переконайтеся, що прапор є безпечним для потоків, використовуючи мінливу змінну або використовуючи методи 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
Що робити, якщо логіка потоку є складною і використовує безліч методів інших класів? Неможливо скрізь перевірити булевий прапор. Що робити тоді?
Сотерик

Вам доведеться змінити конструкцію коду так, щоб ви побудували його таким чином, що сигнал до запущеного сигналу призведе до виходу потоку. У більшості застосувань цей цикл є методом запуску, тому зазвичай не виникає проблеми.
DrYap

3
Що станеться, якщо оператор join () кидає InterruptedException?
benzaita

14
Захищений для поширення поганих порад. підхід ручним прапором означає, що програма повинна чекати, коли сон закінчиться, коли перерва перерве сон. Було б легко змінити це для використання переривання Thread #.
Натан Х'юз

298

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

Якщо ви використовуєте прапор, вам доведеться дочекатися завершення операції блокування, і тоді ви можете перевірити свій прапор. У деяких випадках це все одно доводиться робити, наприклад, використовуючи стандартні InputStream/ OutputStreamякі не перериваються.

У такому випадку, коли нитка перервана, вона не перерве IO, однак, ви можете легко робити це рутинно у своєму коді (і це потрібно робити в стратегічних точках, де ви можете безпечно зупинитись та очистити)

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

Як я вже говорив, головна перевага Thread.interrupt()полягає в тому, що ви можете негайно вирватися з переривчастих дзвінків, чого не можна зробити з підходом до прапора.


32
+1 - Thread.interupt (), безумовно, бажано реалізувати те саме, використовуючи спеціальний прапор.
Стівен C

2
Я також вважаю, що це ідеальний і ефективний спосіб зробити це. +1
RoboAlex

4
У коді невелика помилка помилки, у Thread.currentThread () немає дужок.
Влад V

1
Насправді не бажано використовувати прапор, тому що хтось інший, який контактує з потоком, може перервати його з іншого місця, викликаючи його зупинку і дуже важко налагоджувати. Завжди також використовуйте прапор.
JohnyTex

У цьому конкретному випадку виклик interrupt()може бути нормальним, але у багатьох інших випадках це не так (наприклад, якщо ресурс потрібно закрити). Якщо хтось змінить внутрішню роботу циклу, вам доведеться пам’ятати, щоб змінити interrupt()булевий шлях. Я б пішов безпечним шляхом від самого початку і використовував прапор.
m0skit0

25

Проста відповідь: Ви можете зупинити потік ВНУТРІШНО одним із двох поширених способів:

  • Метод запуску потрапляє у підпрограму повернення.
  • Метод запуску завершується і повертається неявно.

Ви також можете зупинити потоки НАЗВИЧОМ:

  • Виклик system.exit(це вбиває весь ваш процес)
  • Виклик interrupt()методу об’єкта потоку *
  • Подивіться, чи є в потоці реалізований метод, який звучить так, як би це працювало (як kill()або stop())

*: Очікується, що це повинно зупинити нитку. Однак те, що нитка насправді робить, коли це відбувається, повністю залежить від того, що написав розробник, коли створив реалізацію потоку.

Поширений зразок, який ви бачите при реалізації методів запуску, - це while(boolean){}, коли булевим типово називається щось isRunning, воно є змінною члена свого класу потоків, воно є мінливим і зазвичай доступним для інших потоків методом сеттера, наприклад kill() { isRunnable=false; }. Ці підпрограми хороші тим, що вони дозволяють потоку звільнити будь-які ресурси, які він містить до завершення.


3
"Ці підпрограми є приємними, оскільки вони дозволяють потоку звільнити будь-які ресурси, які він містить до завершення." Я не розумію. Ви можете ідеально очистити збережені ресурси потоку, використовуючи статус "офіційного" перерваного стану. Просто перевірте це, використовуючи Thread.currentThread (). IsInterrupted () або Thread.interrupted () (що відповідає вашим потребам), або вловіть InterruptException та очищення. Де проблема?
Франц Д.

Я не зміг зрозуміти, чому працює метод прапора, тому що я не зрозумів, що він зупиняється, коли хід запуску повертається !!! Це було так просто, шановний пане дякую, що ви вказали на це, ніхто цього не робив прямо.
thhgr

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()
Don Cheadle

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
Ніколи не ігноруйте перервану ексцепцію. Це означає, що якийсь інший код явно просить ваш потік припинити. Нитка, яка ігнорує цей запит, є шахрайською ниткою. Правильний спосіб поводження з InterruptException - це вихід із циклу.
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;
 }

Це, швидше за все, не вдасться, оскільки це не повинно 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.