如何中断BufferedReader的readLine

时间:2010-08-29 18:03:34

标签: java multithreading

我试图在多个线程中逐行读取套接字的输入。如何中断readLine()以便我可以优雅地停止它阻塞的线程?

编辑(赏金):这可以在不关闭套接字的情况下完成吗?

10 个答案:

答案 0 :(得分:26)

不关闭套接字:

难题不是BufferedReader.readLine,而是潜在的read。如果一个线程被阻塞读取,唯一的方法是提供一些实际数据或关闭套接字(中断线程可能应该工作,但实际上不行)。

所以显而易见的解决方案是拥有两个线程。一个读取原始数据,并将保持阻止状态。第二个是调用readLine的线程。从第一秒开始管道数据。然后,您可以访问锁定,而不是用于唤醒第二个线程,并让它采取适当的操作。

有变化。您可以使用NIO创建第一个线程,并在所有使用者之间共享一个线程实例。

或者你可以编写一个适用于NIO的readLine。这甚至可以采用相对简单的单线程形式,因为Selector.wakeup存在且有效。

答案 1 :(得分:22)

关闭中断线程上的套接字。这将导致在被中断的线程上抛出异常。

有关此问题和其他并发问题的更多信息,我强烈推荐Brian Goetz的书“Java Concurrency in Practice”。

答案 2 :(得分:7)

我最近玩这个(使用Scala),我不喜欢关闭套接字并获得异常的接受答案。

最终我发现可以在中断线程中调用socket.shutdownInput()以便在没有异常的情况下退出readLine调用。我在一个SIGINT处理程序中进行此调用,以便我可以清理并关闭主线程中的套接字。

请注意,具有socket.shutdownOutput()

的输出流存在等效项

答案 3 :(得分:5)

抱歉超过6年;-)从键盘读取时,我需要一些可中断的readLine,用于简单的爱好控制台应用程序。换句话说,我无法“关闭套接字”。

如您所知,System.inInputStream,显然已经做了一些缓冲(您需要按 Enter ])。但是,似乎建议将其包装在BufferedReader中以提高效率,因此我的输入来自:

BufferedReader consoleIn = new BufferedReader(new InputStreamReader(System.in));

可能发现的另一件事是BufferedReader.readLine()阻塞直到提供输入(即使线程被中断,这似乎只在readline()获得其输入时结束线程)。但是,通过调用BufferedReader.read(),可以预测BufferedReader.ready() == true何时不会阻止。 (但是,== false并不保证阻止,所以要小心。)

所以我将上述想法合并到一个方法中,该方法按字符读取BufferedReader字符,如果线程被中断则检查每个字符,并检查行尾,此时返回文本行。

您可能会发现此代码很有用,如上所述传递consoleIn变量。 (批评也可能受到欢迎......):

private String interruptibleReadLine(BufferedReader reader)
        throws InterruptedException, IOException {
    Pattern line = Pattern.compile("^(.*)\\R");
    Matcher matcher;
    boolean interrupted = false;

    StringBuilder result = new StringBuilder();
    int chr = -1;
    do {
        if (reader.ready()) chr = reader.read();
        if (chr > -1) result.append((char) chr);
        matcher = line.matcher(result.toString());
        interrupted = Thread.interrupted(); // resets flag, call only once
    } while (!interrupted && !matcher.matches());
    if (interrupted) throw new InterruptedException();
    return (matcher.matches() ? matcher.group(1) : "");
}

...在调用它的线程中,捕获异常并适当地结束线程。

这是在Linux上的Java 8中测试的。

答案 4 :(得分:0)

你可以在read()块周围设计一个Timer类。

您需要为计时器设置超时。

on timeout只会中断你的线程。

答案 5 :(得分:0)

在不关闭套接字的情况下,毫无疑问,开销最小的最佳解决方案是在func updateTime() { // Access current item if let currentItem = player.currentItem { // Get the current time in seconds let playhead = currentItem.currentTime().seconds let duration = currentItem.duration.seconds // Format seconds for human readable string playheadLabel.text = formatTimeFor(seconds: playhead) durationLabel.text = formatTimeFor(seconds: duration) } } 准备好或达到超时之前,避免使用阻塞read方法。

BufferedReader

答案 6 :(得分:0)

例如,如果要在客户端-服务器tcp体系结构中的服务器套接字上使用Series,则可以使用Route::get('/home/{company}', 'HomeController@index')->name('home') Route::get('/home',function(){ return redirect()->route('home', \Auth::user()->company); }); 中的readLine

来自Socket#setSoTimeout(int timeout) Documentation

  

启用/禁用具有指定超时的SO_TIMEOUT,以毫秒为单位。将此选项设置为非零超时后,与此套接字关联的InputStream上的read()调用将仅在此时间段内阻塞。如果超时到期,则尽管Socket仍然有效,但会引发 java.net.SocketTimeoutException

setSoTimeout(int timeout)

主应用程序实现了一个服务器套接字,该套接字监听连接并具有线程池。如果接受了传入的客户端通信,则会从池中分配一个新线程,并在java.net.Socket中调用run函数(可以将其调整为允许多个线程)。 在用于与客户端通信的套接字上,已设置属性public class MainApp { public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(10); ServerSocket serverSocket = new ServerSocket(11370); Socket clientSocket = serverSocket.accept(); clientSocket.setSoTimeout(2000); executorService.execute(new ReadingThread(clientSocket)); // ... some async operations executorService.shutdown(); } } public class ReadingThread implements Runnable { private final Socket clientSocket; public ReadingThread(Socket clientSocket) { this.clientSocket = clientSocket; } @Override public void run() { BufferedReader socketReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); String readInput = null; while (!Thread.currentThread().isInterrupted()) { try { readInput = socketReader.readLine(); } catch (SocketTimeoutException e) { continue; } } // operations with readInput } } 。因此,如果ReadingThread在指定的超时时间内未返回,则抛出SocketTimeoutException。 您可以循环检查setSoTimeout(int timeout)是否已被主应用程序中断,如果已中断,请停止从套接字读取。

答案 7 :(得分:0)

当缓冲读取器用于从套接字读取输入流时,您可以通过读取调用超时来实现这一点。一旦触发此超时,您将能够检查您的线程是否应该停止。为此,请在套接字上调用 setSoTimeout。然后,读取调用将有一个 SocketTimeoutException,您可以使用它来停止线程。

@Override
public void run() {
    running = true;

    try {
        socket.setSoTimeout(1000); // This will determine how quick your thread responds to the shutdown call
        var inputStream = socket.getInputStream();
        bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
    } catch (IOException e) {
        Logger.error("IOException while setting up input stream");
        Logger.error(e);
        return;
    }

    StringBuilder stringBuilder = null;
    while (running) {
        try {
            int singleChar = bufferedReader.read();
            // Do something with the data
        } catch (SocketTimeoutException e) {
            // SocketTimeoutException is expected periodically as we do setSoTimeout on the socket, 
            // this makes the above read call not block for ever and allows the loop to be interrupted 
            // cleanly when we want to shut the thread down.
            Logger.trace("Socket timeout exception");
            Logger.trace(e);
        } catch (IOException e) {
            Logger.error("IOException while reading from socket stream");
            Logger.error(e);
            return;
        }
    }
}

public void stopThread() {
    running = false;
    try {
        bufferedReader.close();
    } catch (IOException e) {
        Logger.error("IOException while closing BufferedReader in SocketThread");
        Logger.error(e);
    }
}

在此处找到答案:Any way of using java.nio.* to interrupt a InputStream#read() without closing socket?

答案 8 :(得分:-1)

我认为您可能必须使用readLine()以外的其他内容。您可以使用read()并在每次循环迭代检查时查看线程是否被中断并且如果是,则断开循环。

BufferedReader reader = //...
int c;
while ((c = reader.read()) != -1){
  if (Thread.isInterrupted()){
    break;
  }
  if (c == '\n'){
    //newline
  }
  //...
}

答案 9 :(得分:-1)

解决方案的草图可能是这样的:NIO提供了非阻塞IO的方法,因此您必须实现一个名为Foo的东西,它在套接字端使用非阻塞NIO,但也提供InputStream或{ {1}}另一端的接口。如果Reader输入了自己的BufferedReader,则会调用readFoo会以读取意图调用Selector.selectselect将返回,表示存在更多数据,或者它将阻塞,直到有更多数据可用。

如果另一个线程想要解锁阅读器,它必须调用Selector.wakeup并且选择器可以通过BufferedReader抛出异常来优雅地返回。

之后套接字应该仍然打开。

变体A:调用Selector.select(timeout)来执行繁忙的轮询灯。

相关问题