我试图在多个线程中逐行读取套接字的输入。如何中断readLine()
以便我可以优雅地停止它阻塞的线程?
编辑(赏金):这可以在不关闭套接字的情况下完成吗?
答案 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.in
是InputStream
,显然已经做了一些缓冲(您需要按 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
,则会调用read
,Foo
会以读取意图调用Selector.select
。 select
将返回,表示存在更多数据,或者它将阻塞,直到有更多数据可用。
如果另一个线程想要解锁阅读器,它必须调用Selector.wakeup
并且选择器可以通过BufferedReader
抛出异常来优雅地返回。
之后套接字应该仍然打开。
变体A:调用Selector.select(timeout)
来执行繁忙的轮询灯。