Пробуждение спящего потока - прерывание () против "разделения" сна на несколько снов - PullRequest
5 голосов
/ 03 января 2012

Это требование появилось в моем приложении для Android, но в целом относится к Java. Мое приложение «что-то делает» каждые несколько секунд. Я реализовал это следующим образом (только соответствующие фрагменты - не полный код):

Snippet1:

public class PeriodicTask {

    private boolean running = true;
    private int interval = 5;

    public void startTask(){
        while (running){
            doSomething();
            try{
                Thread.sleep(interval * 1000);
            } catch(InterruptedException e){
                //Handle the exception.
            }
        }
    }

    public void stopTask(){
        this.running = false;
    }

    public void setInterval(int newInterval){
        this.interval = newInterval;
    }
}

Проблема с этим подходом, как вы можете видеть, заключается в том, что setInterval () не сразу эффективен. Действует только после завершения предыдущего сна ().

Поскольку мой вариант использования позволяет конечному пользователю устанавливать интервал фиксированными шагами (от 1 секунды - от 1 до 60 секунд), я изменил реализацию так, чтобы она спала в цикле; и проверяйте новое значение интервала каждую секунду следующим образом:

Snippet2:

public class PeriodicTask {

    private boolean running = true;
    private int interval = 5;
    private int loopCounter = 0;

    public void startTask(){
        while (running){
            doSomething();
            try{
                while(loopCounter < interval) {
                    Thread.sleep(1 * 1000);
                    loopCounter ++;
                }
            } catch(InterruptedException e){
                //Handle the exception.
            }
        }
    }

    public void stopTask(){
        this.running = false;
    }

    public void setInterval(int newInterval){
        synchronized (this) {
            this.interval = newInterval;
            if(newInterval < loopCounter){
                loopCounter = 0;
            }
        }
    }
}

Есть ли причина не использовать этот подход?

Недавно я наткнулся на метод interrupt() для этой цели. Но я не мог точно понять, как его использовать. С одной стороны, метод прерывания, в отличие от метода сна, не равен static. Так что же Thread мне прерывать?

public void setInterval(int newInterval){
        this.interval = newInterval;
        //What thread do I call interrupt() on?
    }

Во-вторых, если мне удастся прервать спящий Thread, я считаю, что блок catch для InterruptedException будет выполнен. Тем не менее, мне нужно будет позвонить startTask() еще раз в этой точке. Я смущен относительно прекращения этой рекурсии. Я прошел через несколько вопросов по SO относительно использования interrupt (), но не смог понять ничего, что мне помогло.

Есть указатели?


РЕДАКТИРОВАТЬ- Более подробная информация о точном требовании:

МОЕ приложение извлекает некоторые значения с помощью вызова REST каждые несколько секунд. Интервал обновления настраивается пользователем.

Теперь, скажем, интервал обновления был установлен на 60 секунд. Snippet1, который я разместил, будет работать (неправильно) следующим образом:

  • Поток засыпает на 60 секунд.
  • Теперь допустим, пользователь изменяет интервал обновления до 5 секунд. Нить все еще спит.
  • PeriodicTask будет видеть новый интервал обновления только после истечения 60 секунд.

Точное требование заключается в том, что новые интервалы обновления должны вступать в силу немедленно (или, по крайней мере, не позднее, чем через 1 секунду после установки - поскольку это то, что пользователь, вероятно, все равно будет воспринимать).

Мои Snippet2 и Snippet3 являются попытками выполнить это требование.

Ответы [ 4 ]

11 голосов
/ 04 января 2012

IIRC, в Java вы можете возразить .wait () с таймаутом. Разве это не то, что вы хотите? Если вы хотите изменить тайм-аут из другого потока, измените некоторую переменную waitValue и notify (). Затем поток «немедленно» запустится, а затем снова будет ждать с новым значением времени ожидания. Не требуется явный сон.

7 голосов
/ 04 января 2012

Этот ответ помог мне сделать работу.Выкладываю некоторый код о том, как я этого добился.Особое значение имеют startTask() и setInterval().

public class PeriodicTask {

    private volatile boolean running = true;
    private volatile int interval = 5;
    private final Object lockObj = new Object();

    public void startTask() {
        while (running) {
            doSomething();
            synchronized (lockObj) {
                try{
                    lockObj.wait(interval * 1000);
                } catch(InterruptedException e){
                    //Handle Exception
                }
            }
        }
    }

    public void stopTask() {
        this.running = false;
    }

    public void setInterval(int newInterval) {
        synchronized (lockObj) {
            this.interval = newInterval;
            lockObj.notify();
        }
    }
}
5 голосов
/ 04 января 2012

Мне не ясно, что вы действительно хотите сделать. Ваша цель - остановить поток, который выполняет цикл в PeriodicTask, или вы просто хотите разорвать цикл и позволить потоку продолжить? Если вы просто хотите разорвать цикл, но позволить потоку продолжить, рассмотрите следующий пример:

public class ThreadStopExample {

    public static void main ( String[] args ) throws InterruptedException {
        final PeriodicTask task = new PeriodicTask ();
        Thread t = new Thread ( new Runnable () {
            @Override
            public void run () {
                System.out.println ( Thread.currentThread ().getName () 
                    + " starting" );
                task.startTask ();
                System.out.println ( Thread.currentThread ().getName ()
                    + " done with the periodic task" );
            }
        } );
        t.start ();
        Thread.sleep ( 12000 );
        task.setInterval ( 1 );
        Thread.sleep ( 3000 );
        task.stopTask ();
    }

    static class PeriodicTask {

        private volatile boolean running = true;
        private volatile int interval = 5;

        public void startTask () {
            running = true;
            while ( running ) {
                doSomething ();
                try {
                    int count = 0;
                    while ( running && count++ < interval ) {
                        Thread.sleep ( 1000 );
                    }
                } catch ( InterruptedException e ) {
                    Thread.currentThread ().interrupt ();
                    running = false;
                    break;
                }
            }
        }

        public void stopTask () {
            running = false;
        }

        public void setInterval ( int newInterval ) {
            interval = newInterval;
        }

        private void doSomething () {
            System.out.println ( "[" + Thread.currentThread ().getName () 
                + "] Interval: " + interval );
        }
    }
}

Это очень похоже на ваш существующий код. Обратите внимание на изменчивые поля для обеспечения правильной синхронизации между потоком, выполняющим цикл PeriodicTask, и основным потоком, который пытается изменить интервал и остановить задачу (см. здесь для ссылок на дополнительную информацию о модели памяти для Java ). Как видите, поток, работающий с экземпляром PeriodicTask, продолжается после вызова, чтобы остановить задачу. Также обратите внимание, что PeriodicTask будет вызывать прерывание в текущем потоке, когда он получит прерванное исключение. Это гарантирует, что флаг прерывания установлен в текущем потоке, чтобы любой внешний код мог видеть прерывание и реагировать соответствующим образом, например, вместо завершения печати поток, выполняющий PeriodicTask, возможно, проверил состояние прерывания и сделал что-то интересное.

Если ваша цель состоит в том, чтобы остановить сам поток, то вы можете захотеть, чтобы PeriodicTask расширял поток, что не рекомендуется, если у вас нет веских оснований для этого, или чтобы PeriodicTask реализовал Runnable. Рассмотрим следующий пример:

public class ThreadStopExample2 {

    public static void main ( String[] args ) throws InterruptedException {
        final PeriodicTask task = new PeriodicTask ();
        Thread t = new Thread ( task );
        t.start ();
        Thread.sleep ( 12000 );
        task.setInterval ( 1 );
        Thread.sleep ( 3000 );
        t.interrupt ();
    }

    static class PeriodicTask implements Runnable {

        private volatile int interval = 5;

        @Override
        public void run () {
            while ( true ) {
                doSomething ();
                try {
                    int count = 0;
                    while ( count++ < interval ) {
                        Thread.sleep ( 1000 );
                    }
                } catch ( InterruptedException e ) {
                    Thread.currentThread ().interrupt ();
                    break;
                }
            }
        }

        public void setInterval ( int newInterval ) {
            interval = newInterval;
        }

        private void doSomething () {
            System.out.println ( "[" + Thread.currentThread ().getName () 
               + "] Interval: " + interval );
        }
    }
}

Здесь PeriodicTask находится в занятом цикле, пока поток, в котором он запущен, не прерван. Прерывание используется для подачи сигнала PeriodicTask на выход из цикла, который затем позволяет завершить поток, проходя через конец метода run.

Относительно ваших двух явных вопросов: нет, я не вижу реальной проблемы с использованием PeriodicTask, как вы, если вы не собираетесь контролировать поток выполнения, например, возможно, экземпляр PeriodicTask запускается потоком в пуле (но обязательно исправьте ваш код для правильной синхронизации), и при использовании прерывания вы вызываете его для экземпляра потока, который вы хотите прервать. То, как вы получите ссылку на этот поток, зависит от вашей системы.

1 голос
/ 03 января 2012

Вы вызываете interrupt() в спящем потоке, и он выдаст InterruptedException, который вы обрабатываете в блоке catch.Тогда у вас есть новый интервал, и вы можете зацикливаться и возвращаться ко сну.Если вы поймаете и обработаете InterruptedException, ничего не произойдет.

Позвольте мне привести несколько ссылок на примеры создания и прерывания потоков, поскольку из ваших комментариев вы, похоже, упускаете некоторые важные идеи.Пожалуйста, внимательно прочитайте их, и тогда вы должны понять стандартный способ сделать то, что вы просите.

http://docs.oracle.com/javase/tutorial/essential/concurrency/simple.html http://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html

...