Java: как прервать чтение потока из System.in

У меня есть поток Java:

class MyThread extends Thread {
  @Override
  public void run() {
    BufferedReader stdin =
        new BufferedReader(new InputStreamReader(System.in));
    String msg;
    try {
      while ((msg = stdin.readLine()) != null) {
        System.out.println("Got: " + msg);
      }
      System.out.println("Aborted.");
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

}

В другом потоке, как мне прервать вызов stdin.readline() в этом потоке, чтобы этот поток печатал Aborted.? Я пробовал System.in.close(), но это не имеет значения, stdin.readline() по-прежнему блокируется.

Меня интересуют решения без

  • занятое ожидание (потому что это сжигает 100% ЦП);
  • спящий (потому что тогда программа не отвечает мгновенно на System.in).

person pts    schedule 15.05.2011    source источник
comment
Интересный. Также было бы интересно узнать, как прервать поток, прослушивающий входящие данные клиентского сокета. Надеюсь, решение очень похоже на эту проблему :)   -  person rzetterberg    schedule 15.05.2011
comment
Это более общий вопрос, чем чтение со стандартного ввода. Материал java.io блокирует ввод-вывод, и есть много существующих вопросов об этой более общей проблеме.   -  person skaffman    schedule 15.05.2011
comment
@Ancide: Для сокетов у меня есть решение: закрытие сокета в другом потоке заставляет readLine в MyThread поднимать SocketException с "Socket closed". Таким образом, я могу прервать MyThread.   -  person pts    schedule 15.05.2011


Ответы (6)


Моя первая реакция заключается в том, что поток и System.in действительно несовместимы.

Итак, сначала разделите это так, чтобы код потока не касался статики, включая System.in.

Поток читает из InputStream и переходит в буфер. Передайте InputStream в ваш существующий поток, который читает из буфера, но также проверяет, не прерваны ли вы.

person Tom Hawtin - tackline    schedule 15.05.2011
comment
Спасибо за предложение не использовать статику в потоках, это хорошее улучшение стиля кодирования. Но это тоже не имеет отношения к моему вопросу. - person pts; 15.05.2011
comment
``также проверяет, что вы не прервали'' -- да, именно это я и хочу сделать, но я не знаю, как это сделать, когда поток заблокирован stdin.readLine(). Не могли бы вы уточнить? - person pts; 15.05.2011
comment
@pts Статика имеет отношение к вашему вопросу. / Два потока: один читает из потока и блокирует ввод-вывод; один (ваш существующий поток) читает из буфера и блокирует блокировку. - person Tom Hawtin - tackline; 15.05.2011
comment
Было бы неплохо увидеть пример кода, реализующего эту идею. Ваше описание немного расплывчато. Я ответил на похожий вопрос и предоставил простой код, использующий PipedInputStream в качестве примера. - person Patrick Parker; 28.03.2018

В бюллетене Хайнца Кабуца показано, как прервать System.in чтения с помощью buffer и ExecutorService.

Теперь я не знаю, дает ли этот подход утечку, не является переносимым или имеет какие-либо неочевидные побочные эффекты. Лично я бы не стал его использовать.

Возможно, вы сможете что-то сделать с помощью Каналы NIO и файловые дескрипторы - мои собственные эксперименты с ними не дали результатов.

person McDowell    schedule 15.05.2011
comment
Спасибо за эти предложения. Я также думаю, что это может как-то работать с NIO, но было бы слишком много работы, чтобы переписать мою программу для использования NIO, поэтому я сейчас ищу другие варианты. ExecutorService, как вы его описываете, определенно сработает, но я все еще ищу что-то менее уродливое. - person pts; 15.05.2011
comment
привет, извините меня, если я неправильно понял, после прочтения статьи, похоже, предлагается не столько ExecutorService (который используется для получения результата из потока), сколько больше об использовании BufferedStream ready() и спящем режиме, чтобы поток мог быть прерываемым. - person goh; 24.10.2012
comment
@goh - Еще раз взглянув на мой код, я считаю, что вы правы. Я удалю образец и оставлю ссылку. - person McDowell; 24.10.2012
comment
На самом деле он просто использует Thread.sleep(200) ... не знаю, почему этот ответ получил наибольшее количество голосов. - person Patrick Parker; 28.03.2018

Как насчет...

private static BufferedReader stdInCh = new BufferedReader(
    new InputStreamReader(Channels.newInputStream((
    new FileInputStream(FileDescriptor.in)).getChannel())));

Поток, в котором вызывается stdInch.readline(), теперь может быть прерван, и readline() выдаст java.nio.channels.ClosedByInterruptException.

person Chris Sherman    schedule 13.12.2011
comment
Не работает, например, в Mac OS X с JDK 1.7. readLine() не реагирует на прерывание(). - person Mostowski Collapse; 12.03.2012
comment
То же самое :( Я верил в неблокировку - person dinigo; 30.04.2013
comment
Работает на Linux и OpenJDK 1.8. Спасибо. - person Vladimir Petrakovich; 25.09.2016

JavaDoc для BufferedReader.readLine :

Возвращает: Строка, содержащая содержимое строки, не включая никаких символов завершения строки, или нуль, если достигнут конец потока.

Исходя из этого, я не думаю, что он когда-либо вернет null (может ли System.in быть закрыт, я не думаю, что он когда-либо возвращает конец потока?), поэтому цикл while не завершится. Обычный способ остановить поток — либо использовать логическую переменную в условии цикла и изменить ее вне потока, либо вызвать метод interrupt() объекта Thread (работает, только если поток ждет():ing или sleep():ing или в методе блокировки, который выдает InterruptedException). Вы также можете проверить, был ли поток прерван, с помощью isInterrupted().

Изменить: вот простая реализация с использованием isInterrupted() и interrupt(). Основной поток ждет 5 секунд, прежде чем прервать рабочий поток. В этом случае рабочий поток в основном занят ожиданием, так что это не так уж и хорошо (постоянно зацикливаясь и проверяя stdin.ready(), вы, конечно, можете позволить рабочему потоку заснуть на некоторое время, если никакие входные данные не готовы):

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;


public class MyThreadTest
{
    public static void main(String[] args)
    {
        MyThread myThread = new MyThread();
        myThread.start();

        try
        {
            Thread.sleep(5000);
        }
        catch(InterruptedException e)
        {
            //Do nothing
        }

        myThread.interrupt();

    }

    private static class MyThread extends Thread
    {       
        @Override
        public void run()
        {
            BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
            String msg;

            while(!isInterrupted())
            {
                try
                {
                    if(stdin.ready())
                    {
                        msg = stdin.readLine();
                        System.out.println("Got: " + msg);
                    }
                }
                catch(IOException e)
                {
                    e.printStackTrace();
                }
            }           
            System.out.println("Aborted.");
        }
    }

}

Кажется, нет никакого способа прервать BufferedReader, если он заблокирован в строке чтения, или, по крайней мере, я не смог его найти (используя System.in).

person esaj    schedule 15.05.2011
comment
Спасибо за составление вашего ответа, но это не решает мою проблему. Вызов myThread.interrupt() не прерывает вызов readLine() System.in даже после System.in.close(). - person pts; 15.05.2011
comment
Не работает в Windows. Windows не повторяет ввод во время ready(), поэтому не подходит для интерактивного решения. - person Mostowski Collapse; 12.03.2012

Ты пытался:

Thread myThread = new MyThread();
myThread.start();
// your code.
myThread.interrupt();

Метод прерывания вызовет исключение InterrupedtException, после чего вы сможете обрабатывать код.

person Pih    schedule 15.05.2011
comment
Прерываемый ввод-вывод был кратковременно включен в Solaris, но обычно не работает. (Прерывания в любом случае ужасны.) - person Tom Hawtin - tackline; 15.05.2011
comment
Это ужасно, но он спросил: как мне прервать stdin.readline(), значит, это способ сделать это. Идея чтения System.in внутри этого потока неверна. - person Pih; 15.05.2011
comment
Прерывание не обязательно подразумевает механизм прерывания потока Java. - person Tom Hawtin - tackline; 15.05.2011
comment
Спасибо, что пришли с этим предложением, но оно не работает для меня. myThread.interrupt() не имеет значения. stdin.readLine() по-прежнему заблокирован, ожидая чтения дополнительных данных от System in. - person pts; 15.05.2011

Как насчет определения поля в указанном выше определении класса потока, например:

class MyThread extends Thread {   

  protected AtomicBoolean abortThread = new AtomicBoolean(false);

  public void doAbort()
  {
    this.abortThread.set(true);
  }

  @Override   public void run() 
  { 
    ...
    if (this.abortThread.get())
    {
      ...something like break loop...
    }
  }
}
person Omnaest    schedule 15.05.2011
comment
помогает ли вызов System.in.ready() перед фактическим чтением из потока? - person Omnaest; 15.05.2011
comment
@ user625146: Спасибо за ваше предложение, но оно не работает. Я не понимаю, как установка abortThread прервет вызов System.in.readline(). - person pts; 15.05.2011
comment
@ user625146: Что, если ready() возвращает false? Как заставить MyThread ждать следующей строки без ожидания (100% использование ЦП)? - person pts; 15.05.2011
comment
Перевод потока в спящий режим на заданное время. Это можно сделать с помощью второго окружающего цикла while. Вы входите в свой цикл чтения, только если читатель готов, если вы не переводите свой поток в режим сна. Это работает? - person Omnaest; 15.05.2011