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% CPU);
  • 睡眠(因为这样程序不会立即响应System.in)。

Heinz Kabutz的时事通讯显示了如何中止System.in内容如下:

import java.io.*;
import java.util.concurrent.*;
class ConsoleInputReadTask implements Callable<String> {
  public String call() throws IOException {
    BufferedReader br = new BufferedReader(
        new InputStreamReader(System.in));
    System.out.println("ConsoleInputReadTask run() called.");
    String input;
    do {
      System.out.println("Please type something: ");
      try {
        // wait until we have data to complete a readLine()
        while (!br.ready()) {
          Thread.sleep(200);
        }
        input = br.readLine();
      } catch (InterruptedException e) {
        System.out.println("ConsoleInputReadTask() cancelled");
        return null;
      }
    } while ("".equals(input));
    System.out.println("Thank You for providing input!");
    return input;
  }
}
public class ConsoleInput {
  private final int tries;
  private final int timeout;
  private final TimeUnit unit;
  public ConsoleInput(int tries, int timeout, TimeUnit unit) {
    this.tries = tries;
    this.timeout = timeout;
    this.unit = unit;
  }
  public String readLine() throws InterruptedException {
    ExecutorService ex = Executors.newSingleThreadExecutor();
    String input = null;
    try {
      // start working
      for (int i = 0; i < tries; i++) {
        System.out.println(String.valueOf(i + 1) + ". loop");
        Future<String> result = ex.submit(
            new ConsoleInputReadTask());
        try {
          input = result.get(timeout, unit);
          break;
        } catch (ExecutionException e) {
          e.getCause().printStackTrace();
        } catch (TimeoutException e) {
          System.out.println("Cancelling reading task");
          result.cancel(true);
          System.out.println("nThread cancelled. input is null");
        }
      }
    } finally {
      ex.shutdownNow();
    }
    return input;
  }
}

现在,我不知道这种方法是否泄漏,不便携或有任何不明显的副作用。就个人而言,我不愿意使用它。

你也许可以用NIO通道和文件描述符做一些事情——我自己对它们的实验没有产生任何结果。

怎么样...

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

调用 stdInch.readline() 的线程现在是可中断的,readline() 将抛出一个java.nio.channels.ClosedByInterruptException

InputStream永远不会抛出InterruptedException,因为它不在它的合约中。(你应该在阅读之前继续检查available事实上,大多数类和方法的契约中都没有这个例外!(在大多数情况下,他们懒得打电话给available

一个更大的问题是,InterruptibleChannel(合同中确实有)也不能保证成功。一些通道谎称可中断,而使用这些通道的类无论如何都可能会阻塞。例如,您可能认为Channels.newChannel(System.in)会让您InterruptibleChannel不错。但这是一个谎言,源代码注释"不是真正可中断"和"最多阻止一次"(OpenJDK 16)证明了这一点。(是的,它确实阻止了,我已经检查过了。可笑!

我发现有效的一种组合是将new FileInputStream(FileDescriptor.in).getChannel()Scanner一起使用。

Scanner scanner = new Scanner(new FileInputStream(FileDescriptor.in).getChannel())
while (!scanner.hasNextLine())
    Thread.sleep(100); // Internally checks Thread.interrupted() and throws InterruptedException
String line = scanner.nextLine()

这真的不应该是一个问题。编写一个检查System.in.available()Thread.interrupted()、抛出InterruptedException等的类很简单,🤷甚至 Scanner 也不会检查available是否给定InputStream或阻塞模式下的Channel(从 OpenJDK 16 开始)。如果您知道一个理智的课程,请发表评论。

我的第一反应是线程System.in真的不能放在一起。

因此,首先,将其拆分,以便线程代码不会触及任何静态,包括System.in

线程从InputStream读取并传递到缓冲区中。将InputStream传递到现有线程中,该线程从缓冲区读取,但还会检查是否未中止。

JavaDoc for BufferedReader.readLine:

返回: 包含行内容的字符串,不包括任何 行终止字符或空 如果流的末尾 达到

基于此,我认为它永远不会返回 null(System.in 实际上可以关闭吗,我认为它永远不会返回流的结束?),所以 while-loop 不会终止。停止线程的常用方法是在循环条件中使用布尔变量并从线程外部更改它,或者调用 Thread-对象的 interrupt() -方法(仅当线程是 wait():ing 或 sleep():ing 时有效,或者在抛出 InterruptedException 的阻塞方法中)。您还可以检查线程是否已使用 isInterrupted() 中断。

编辑:这是一个利用isInterrupted()interrupt()的简单实现。主线程在中断工作线程之前等待 5 秒。在这种情况下,worker-thread基本上是忙碌的等待,所以它不是那么好(一直循环并检查stdin.ready(),如果没有输入,你当然可以让worker线程休眠一段时间):

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 在 readline 上被阻止,或者至少我找不到一个(使用 System.in),似乎没有办法实际中断它。

在上面的线程类定义中定义字段有什么好处,例如:

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...
    }
  }
}

最新更新