Java:如何中止从System.in读取的线程

16

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

有趣。知道如何中止一个正在监听传入客户端套接字数据的线程也很有趣。希望解决方案与此问题非常相似 :) - rzetterberg
这是一个更一般性的问题,而不是从stdin读取的问题 - java.io的内容是阻塞I/O,并且已经有许多关于这个更一般的问题的现有问题了。 - skaffman
@Ancide:对于套接字,我有一个解决方案:在另一个线程中关闭套接字会使MyThread中的readLine引发一个带有“Socket closed”的SocketException。因此,我可以通过这种方式中止MyThread - pts
6个回答

9

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通道文件描述符做一些事情-我的实验没有产生任何结果。


谢谢您的建议。我也认为它可能与NIO一起使用,但是重写我的程序以使用NIO将需要太多工作,因此我现在正在寻找其他选项。正如您所描述的那样,ExecutorService肯定可以工作,但我仍在寻找更少丑陋的东西。 - pts
2
抱歉,如果我理解有误,在阅读文章后,似乎它所建议的并不是关于ExecutorService(用于从线程中获取结果),而更多地是关于使用BufferedStream ready()和sleeping,以便线程可以被中断。 - goh
@goh - 再次查看我的代码,我相信你是正确的。我会删除示例并保留链接。 - McDowell
@eis - 被问到的问题是:“我对不需要睡眠的解决方案很感兴趣”。 - Patrick Parker
@PatrickParker 确实,我没有注意到。然而,在任何正常情况下,200毫秒的睡眠时间都不应该是什么问题。 - eis
显示剩余7条评论

4

你觉得...

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

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


在Mac OS X上使用JDK 1.7时不起作用。readLine()没有对interrupt()做出反应。 - user502187
我也一样:( 我对非阻塞有信心。 - dinigo
适用于Linux和OpenJDK 1.8。谢谢。 - Vladimir Petrakovich
这种方法很愚蠢。InputStream 方法中没有一个会抛出 InterruptedException,因此 ChannelInputStream(由 Channels.newInputStream 返回)尽力恢复通常的阻塞行为。 - Aleksandr Dubinsky

2

InputStream无法抛出InterruptedException,因为在其协议中没有规定。(读取之前应该不断检查available。)实际上,大多数类和方法的协议中也没有这个异常!(而且大多数情况下它们都不会调用available。)

更大的问题是,InterruptibleChannel(它的协议中确实有这个异常)也不能保证成功。有些通道撒谎声称可以被中断,而使用这些通道的类可能仍然会阻塞。例如,您可能认为Channels.newChannel(System.in)会得到一个漂亮的InterruptibleChannel。但是,源代码注释“Not really interruptible”和“Block at most once”(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等的类很简单。即使给定一个InputStream或阻塞模式下的Channel(截至OpenJDK 16),甚至Scanner也不会检查available。如果您知道一个合理的类,请在评论中提出。

1

我的第一反应是,线程System.in真的不搭配。

所以首先,将此拆分,使线程代码不接触任何静态内容,包括System.in

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


谢谢您建议不要在线程中使用静态变量,这是一个很好的编码风格改进。但这与我的问题无关。 - pts
3
“also checks that you haven't aborted” 的意思是“还要检查您是否已中止”,但当线程被阻塞在“stdin.readLine()”时,我不知道如何实现这一点。您可以详细说明吗? - pts
1
@pts 静态变量与您的问题相关。/ 两个线程:一个从流中读取并在I/O上阻塞;另一个(您现有的线程)从缓冲区中读取并在锁上阻塞。 - Tom Hawtin - tackline
看到一些实现这个想法的示例代码会很好。您的描述有点模糊。我回答了一个类似的问题,并提供了一些使用PipedInputStream作为示例的简单代码。 - Patrick Parker

0

BufferedReader.readLine的JavaDoc

返回: 包含行内容(不包括任何行终止字符)的字符串,或者如果已到达流的末尾,则为null

基于此,我认为它永远不会返回null(System.in是否可以关闭?我认为它永远不会返回流的末尾),因此while循环将不会终止。停止线程的通常方法是在循环条件中使用布尔变量,并从线程外部更改它,或调用Thread对象的interrupt()方法(仅在线程处于wait()、sleep()或抛出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)。


1
感谢您撰写答案,但它并没有解决我的问题。调用myThread.interrupt()不能打断System.in上的readLine()调用,即使是在System.in.close()之后也不行。 - pts
不适用于Windows。在ready()期间,Windows不会回显输入,因此不适合交互式解决方案。 - user502187

-2

在你上面的线程类定义中定义一个字段怎么样:

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() 有帮助吗? - Omnaest
@user625146:感谢您提出建议,但它并不起作用。我无法理解设置“abortThread”如何中止对“System.in.readline()”的调用。 - pts
如果ready()返回false怎么办?我应该如何让MyThread等待下一行,而不是忙等待(占用100%的CPU)? - pts
通过预定义的时间量将线程置于休眠状态。这可以通过第二个while循环来完成。只有在读取器准备好时,您才会进入读取while循环,如果不是,则将线程置于休眠状态。这样行得通吗? - Omnaest

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接