Вызывающий поток.прерывание в классе между операциями ввода-вывода (является ли это условием гонки)?

#java #android #multithreading

#java #Android #многопоточность

Вопрос:

Мой вопрос специфичен для Android, но я подозреваю, что он применим к потокам Java в целом.

Допустим, цикл выполнения рабочего потока выполняет это. Чтение байтов из сетевого потока и выполнение чего-либо с каждым прочитанным фрагментом.

  class MyWorker implements Runnable {

     boolean _exitFlag;
     Thread _thread;


     void run()
     {
         HttpUrlConnection connection;

         // connection establishment not shown for brevity

         while(_exitFlag == false)
         {
              InputStream stream = connection.getInputStream();
              int resu<
              try
              {
                  result = stream.read(bytearray);
                  if (result == -1)
                      break;
                  processBytes(bytearray, result); 
              }
              catch(IOException ioex)
              {
                  break;                  
              }
         }
     }
  

Теперь другой поток готов подать сигнал этому потоку о завершении, вызвав другой метод в этом классе:

 public void stop()
{
    _exitFlag = true;
    _thread.interrupt();
    _thread.join();
}
  

Насколько я понимаю, _thread.interrupt вызов вызовет блокирующую операцию ввода-вывода при stream.read вызове с исключением, производным от IOException. Это предполагает, что поток действительно находится в этой точке кода.

Допустим, stop вызов был выполнен сразу после того, как рабочий поток проверил _exitFlag==false условие, но до того, как он перешел к последующему read вызову?

Будет ли последующий вызов stream.read() немедленно выдавать исключение, если поток имеет отправленное прерывание? Или он будет продолжать блокировать и ждать завершения операции ввода-вывода? Другими словами, это условие гонки?

Какой хороший шаблон для сигнализации рабочему потоку, который может находиться в середине длительной блокирующей операции ввода-вывода, о выходе?

Ответ №1:

Рассмотрим следующий код

 package test;

public class BlockingTest {

    public static void main(String[] args) {
        Thread t = new Thread(new Sleeper());
        t.start();
        System.out.println("interrupting");
        t.interrupt();
        try {

            t.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static class Sleeper implements Runnable {
        private final Object lock = new Object();

        @Override
        public void run() {
            try {
                for (int I = 0; I < 120000; I  ) {
                    System.out.println(I);
                }
                System.out.println("Stopped counting, waiting");
                synchronized (lock) {
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }   

    }

}
  

Здесь поток, запущенный в Sleeper, выдаст прерванное исключение при входе в монитор объекта блокировки. .interrupt() был вызван основным потоком задолго до того, как он дошел до этой точки. Таким образом, вызов interrupt в потоке перед вводом некоторой операции блокировки вызовет прерываемое исключение, если вызов прерывания этого потока был выполнен до операции блокировки. Полезно отметить, что вызов interrupted в потоке снимет флаг interrupted, поэтому, если это произойдет:

 Thread.interrupt();
....
Thread.interrupted();
Thread.interrupted();
  

Второй вызов Thread.interrupted() вернет false .
Вот документ: http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#interrupted()

Я помню, как давно слышал, что не стоит полагаться на прерывания в качестве условий выхода. Обычно я реализую метод exit (), который устанавливает некоторый флаг выхода и вызывает прерывание в рассматриваемом потоке. Это гарантирует, что я либо оцениваю флаг выхода и завершаю работу, либо, если поток ожидает в цикле на каком-либо мониторе, он выдаст исключение IOException, а затем оценит флаг выхода и завершит работу. Но я могу представить ситуации, когда вы хотите установить флаг, но не обязательно прерывать, например. обеспечение того, чтобы поток не прервал операцию записи и, скажем, не повредил некоторые сохраненные данные при записи.

Ответ №2:

Это разные вещи. Вызов Thread.interrupt устанавливает флаг только в целевом потоке. Этот поток должен обработать вызов. Если поток находится в спящем режиме, то будет выдано InterruptedException, и целевой поток должен обработать исключение (или выдать его снова). Однако, если целевой поток не находится в состоянии, вызывающем исключение InterruptedException (не спящий режим и не присоединение), тогда целевой поток должен постоянно проверять наличие сигнала через Thread.interrupted() .

В вашем примере поток, считывающий поток, должен периодически проверять, был ли установлен interrupted флаг или нет.

В вашем коде я бы проверил флаг, используя что-то вроде

               result = stream.read(bytearray);
              if (result == -1)
                  break;
              processBytes(bytearray, result);
              if (Thread.interrupted()) {
                  // We've been interrupted: no more crunching.
                  // handle the interruption here by throwing an Exception or returning.. or something else
               }
  

Комментарии:

1. Спасибо. Я уже все это знал. Но даже с тем модом, который у вас есть, все еще существует потенциальное условие гонки. Вызов Thread.interruped() может возвращать false, за которым следует переключение контекста на другой поток, который затем вызывает thread.interrupt , затем переключение контекста обратно на рабочий поток, который теперь снова переходит на read вызов. read В конечном итоге вызывает Thread . Выполняется ли функция interrupted() перед ее блокировкой? Это действительно вопрос.

2. Верно. Вот почему я хотел бы добавить свой собственный метод ‘beenInterrupted`, который будет проверять и оставаться проверенным, interrupted был ли когда-либо вызван.

3. Это то же самое, что _exitFlag в моем примере кода. Это все еще не решает условие гонки, если read не проверяет условие прерывания перед блокировкой. Я думаю, что решение состоит в том, чтобы включить stop цикл метода thread.interrupt() и заблокировать его thread.join только на долю секунды.

4. Да, это сработает, если потоку не нужно выполнять никакой работы по очистке.

Ответ №3:

Вызывающие методы InterruptedException проверят перед блокировкой, прерван ли поток. Из документов InterruptedException :

Вызывается, когда поток ожидает, находится в спящем режиме или иным образом занят, и поток прерывается либо до, либо во время выполнения операции.

(Выделено мной)

Просто попробуйте это:

 public static void main(String[] args) throws InterruptedException {
  Thread.currentThread().interrupt();
  Thread.sleep(1000);
}
  

Результат:

 Exception in thread "main" java.lang.InterruptedException: sleep interrupted
    at java.base/java.lang.Thread.sleep(Native Method)
    at ...
  

Это означает, что вы можете отказаться от самостоятельной проверки флага (которая в любом случае была бы подвержена всевозможным условиям гонки), если вы часто вызываете другой метод, который выдает InterruptedException .