java - вопрос об аборте и тупике потока - изменчивое ключевое слово - PullRequest
2 голосов
/ 03 июня 2010

У меня возникли проблемы с пониманием того, как я должен остановить работающий поток. Я постараюсь объяснить это на примере. Предположим, следующий класс:

public class MyThread extends Thread {
    protected volatile boolean running = true;

    public void run() {
        while (running) {
            synchronized (someObject) {
                while (someObject.someCondition() == false && running) {
                    try {
                        someObject.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // do something useful with someObject
            }
        }
    }

    public void halt() {
        running = false;
        interrupt();
    }
}

Предположим, что поток работает, и следующий оператор оценивается как true:

while (someObject.someCondition() == false && running)

Затем другой поток вызывает MyThread.halt (). Несмотря на то, что эта функция устанавливает для параметра «running» значение false (которое является логическим значением volatile) и прерывает поток, следующий оператор все еще выполняется:

someObject.wait();

У нас тупик. Поток никогда не будет остановлен.

Тогда я придумал это, но я не уверен, правильно ли это:

public class MyThread extends Thread {
    protected volatile boolean running = true;

    public void run() {
        while (running) {
            synchronized (someObject) {
                while (someObject.someCondition() == false && running) {
                    try {
                        someObject.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // do something useful with someObject
            }
        }
    }

    public void halt() {
        running = false;
        synchronized(someObject) {
            interrupt();
        }
    }
}

Это правильно? Это самый распространенный способ сделать это?

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

Ответы [ 4 ]

2 голосов
/ 03 июня 2010

Вызов interrupt () установит флаг в прерванном потоке, someObject.wait () будет всегда проверять этот флаг, поэтому ваш первый класс должен работать. AFAIC первый способ, ваша ошибка должна быть где-то еще.

1 голос
/ 03 июня 2010

Я попытался смоделировать первую версию, и действительно, флаг прерывания запоминается. Я не знал этого. Вот мой код, как я его симулировал:

public class Test {
    protected static class MyThread extends Thread {
        protected Object someObject = new Object();

        public void run() {
            for (int i = 0; i < Integer.MAX_VALUE; ++i) {
                /* this takes some time */
            }
            try {
                synchronized (someObject) {
                    someObject.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("WE COME HERE AFTER INTERRUPTED EXCEPTION");
            try {
                synchronized (someObject) {
                    someObject.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("HOWEVER, WE NEVER COME HERE, THE INTERRUPTED FLAG SEEMS TO BE RESETTED");
        }

        public void halt() {
            interrupt();
        }
    }

    public static void main(String[] a) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        t.halt();
    }
}

Это заставляет меня задуматься. Почему

public void halt() {
    interrupt();
}

предпочтительнее выше

public void halt() {
    synchronized(someObject) {
        someObject.notifyAll();
    }
}

В обеих версиях while (...) будет оцениваться снова?

0 голосов
/ 03 июня 2010

В качестве альтернативы .... вы можете использовать метод ожидания перегрузки, который принимает в качестве ввода максимальные миллисекунды ожидания. Теперь в вашем методе halt вы можете просто установить running=false и быть уверенным, что через указанные миллисекунды вызов ожидания завершится, и пока условие будет оценено снова.

В приведенном ниже модифицированном коде ожидание будет блокироваться не более 1 секунды, а затем снова будет проверяться состояние цикла while. На этот раз, когда для работы установлено значение false, цикл завершится.

synchronized (someObject) {
    while (someObject.someCondition() == false && running) {
        try {
            someObject.wait(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
    }
}

ПРИМЕЧАНИЕ: Этот подход может быть не лучшим в высокопроизводительных потоках, но я нашел его полезным во многих ситуациях.

BTW - если JVM выполняет логику, которая в основном не блокирует (не прерывается), такую ​​как условие цикла while в вашем коде, , тогда прерывание, инициируемое методом остановки быть "потерянным" . И вам придется полагаться на флаг isInterrupted, чтобы сообщить логике, было ли вызвано прерывание или нет.

0 голосов
/ 03 июня 2010

В блоке catch для InterruptedException необходимо указать:

if (!running)
    break main;

и обозначьте вашу while (running) петлю как main: while (running)

Нет необходимости синхронизироваться на someObject для вызова interrupt.

Кроме того, я предлагаю переименовать вашу переменную running, потому что это очень запутанно. Я предлагаю shouldContinue. Таким образом:

public class MyThread extends Thread {
    private volatile boolean shouldContinue = true;

    public void run() {
        main: while (shouldContinue) {
            synchronized (someObject) {
                while (someObject.someCondition() == false && shouldContinue) {
                    try {
                        someObject.wait();
                    } catch (InterruptedException e) {
                        if (!shouldContinue) {
                            break main;
                        }
                    }
                }
                // do something useful with someObject
            }
        }
    }

    public void halt() {
        shouldContinue = false;
        interrupt();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...