【问题标题】:Java: how to abort a thread reading from System.inJava:如何中止从 System.in 读取的线程
【发布时间】:2011-08-25 21:01:51
【问题描述】:

我有一个 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)。

【问题讨论】:

  • 有趣。知道如何中止监听传入客户端套接字数据的线程也会很有趣。希望解决方案与这个问题非常相似:)
  • 这是一个从标准输入读取的更普遍的问题 - java.io 的东西正在阻塞 I/O,关于这个更普遍的问题存在许多问题。
  • @Ancide:对于套接字,我有一个解决方案:在另一个线程中关闭套接字会使MyThread 中的readLine 引发SocketException"Socket closed"。所以我可以通过这种方式中止MyThread

标签: java stdio


【解决方案1】:

Heinz Kabutz's newsletter 显示如何中止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 channelsfile descriptors 做点什么——我自己对它们的实验没有产生任何结果。

【讨论】:

  • 感谢您的这些建议。我也认为它可以以某种方式与 NIO 一起工作,但是重写我的程序以使用 NIO 工作量太大,所以我现在正在寻找其他选项。 ExecutorService,正如你所描述的,肯定会起作用,但我仍在寻找不那么丑陋的东西。
  • 嗨,如果我理解不正确,请原谅我,在阅读了这篇文章之后,似乎没有太多关于 ExecutorService (用于从线程中获取结果)的建议,而是更多关于使用 BufferedStream ready( ) 并休眠,以便线程可以被中断。
  • @goh - 再次查看我的代码,我相信您是正确的。我将删除示例并留下链接。
  • 确实它只是在使用Thread.sleep(200)...不知道为什么这是投票最多的答案。
  • @PatrickParker 在这里使用 thread.sleep 有什么问题?它实现了想要的目标,让线程处理任何中断。这就是 IMO 应该这样做的方式。
【解决方案2】:

怎么样...

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

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

【讨论】:

  • 例如在带有 JDK 1.7 的 Mac OS X 上不起作用。 readLine() 对 interrupt() 没有反应。
  • 这里也一样:(我相信非阻塞
  • 适用于 Linux 和 OpenJDK 1.8。谢谢。
  • 这种方法很愚蠢。 InputStream 方法都不会抛出 InterruptedException,因此 ChannelInputStream(由 Channels.newInputStream 返回)会尽力恢复通常的阻塞行为。
【解决方案3】:

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

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

一个线程从InputStream 读取数据并传递到一个缓冲区。将InputStream 传递到从缓冲区读取的现有线程中,但还会检查您是否没有中止。

【讨论】:

  • 感谢您建议不要在线程中使用静态,这是一个很好的编码风格改进。但这也与我的问题无关。
  • ``还检查你没有中止'' - 是的,这正是我想做的,但是当线程被阻塞stdin.readLine() 时我不知道该怎么做。能详细点吗?
  • @pts Statics 与您的问题相关。 / 两个线程:一个从流中读取并在 I/O 上阻塞;一个(您现有的线程)从缓冲区读取并锁定锁。
  • 很高兴看到一些实现这个想法的示例代码。你的描述有点模糊。我回答了similar question,并提供了一些简单的代码,以 PipedInputStream 为例。
【解决方案4】:

JavaDoc for BufferedReader.readLine:

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

基于此,我认为它永远不会返回 null(System.in 实际上可以关闭吗,我认为它永远不会返回流的结尾?),所以 while 循环不会终止.停止线程的常用方法是在循环条件中使用布尔变量并从线程外部更改它或调用线程对象的 interrupt() -方法(仅在线程处于 wait() 时才有效: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 在 readline 上被阻塞,似乎没有办法真正中断它,或者至少我找不到(使用 System.in)。

【讨论】:

  • 感谢您撰写答案,但这并不能解决我的问题。调用myThread.interrupt() 不会中断readLine()System.in 的调用,即使在System.in.close() 之后也不会。
  • 不适用于 Windows。 Windows 在 ready() 期间不回显输入,因此不适合交互式解决方案。
【解决方案5】:

InputStream 永远不能抛出 InterruptedException 因为它不在它的合同中。 (你应该在阅读之前继续检查available。)事实上,大多数类和方法都没有这个例外!(而且在大多数情况下,他们不会打扰调用available.)

更大的问题是InterruptibleChannel(合同中确实有它)也不能保证成功。一些 Channel 谎称是可中断的,而使用这些通道的类可能会阻塞。例如,您可能认为Channels.newChannel(System.in) 会给您一个不错的InterruptibleChannel。但源代码 cmets “不是真正可中断”和“最多阻塞一次”(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 等的类很简单。? 如果在阻塞模式下给定InputStreamChannel,即使扫描仪也不会检查available(如OpenJDK 16)。如果您知道有一个理智的课程,请发表评论。

【讨论】:

    【解决方案6】:

    关于在上面的线程类定义中定义一个字段,例如:

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

    【讨论】:

    • 在实际读出流之前调用 System.in.ready() 有帮助吗?
    • @user625146:感谢您提出您的建议,但它不起作用。我看不到设置abortThread 会如何中止对System.in.readline() 的调用。
    • @user625146:如果 ready() 返回 false 怎么办?如何让MyThread 等待下一行,而不是忙于等待(100% CPU 使用)?
    • 在预定义的时间内让您的线程进入睡眠状态。这可以通过第二个环绕的 while 循环来完成。只有当读者准备好时,你才进入你的 read-while-loop,否则你让你的线程进入睡眠状态。这行得通吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-08
    • 1970-01-01
    • 2011-07-26
    • 1970-01-01
    • 2011-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多