如何中断 System.in 上的阅读?
How to interrupt reading on System.in?
如果我从 System.in
开始读取,它将阻塞线程直到它获取数据。没有办法阻止它。以下是我尝试过的所有方法:
- 中断线程
- 停止线程
- 关闭
System.in
- 调用
System.exit(0)
确实停止了线程,但它也杀死了我的应用程序,所以不理想。
- 在控制台中输入一个字符生成方法 return,但我不能依赖用户输入。
无效的示例代码:
public static void main(String[] args) throws InterruptedException {
Thread th = new Thread(() -> {
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
});
th.start();
Thread.sleep(1000);
System.in.close();
Thread.sleep(1000);
th.interrupt();
Thread.sleep(1000);
th.stop();
Thread.sleep(1000);
System.out.println(th.isAlive()); // Outputs true
}
当我运行这段代码时,它将永远输出true
和运行。
如何以可中断的方式从 System.in
读取?
您应该设计 运行 方法,使其能够自行确定何时终止。在线程上调用 stop() 或类似方法将是 inherently unsafe.
但是,如何避免内部阻塞的问题仍然存在System.in.read?为此,您可以在阅读之前轮询 System.in.available 直到 returns > 0。
示例代码:
Thread th = new Thread(() -> {
try {
while(System.in.available() < 1) {
Thread.sleep(200);
}
System.in.read();
} catch (InterruptedException e) {
// sleep interrupted
} catch (IOException e) {
e.printStackTrace();
}
});
当然,通常认为使用阻塞 IO 方法比轮询更有利。但是轮询确实有它的用处。在你的情况下,它允许这个线程干净地退出。
更好的方法:
避免轮询的 better approach 将重组代码,以便不允许您打算终止的任何线程直接访问 System.in
。这是因为 System.in 是一个不应关闭的 InputStream。相反,主线程或另一个专用线程将从 System.in (阻塞)中读取,然后将任何内容写入缓冲区。反过来,该缓冲区将由您打算杀死的线程监控。
示例代码:
public static void main(String[] args) throws InterruptedException, IOException {
PipedOutputStream stagingPipe = new PipedOutputStream();
PipedInputStream releasingPipe = new PipedInputStream(stagingPipe);
Thread stagingThread = new Thread(() -> {
try {
while(true) {
stagingPipe.write(System.in.read());
}
} catch (IOException e) {
e.printStackTrace();
}
});
stagingThread.setDaemon(true);
stagingThread.start();
Thread th = new Thread(() -> {
try {
releasingPipe.read();
} catch (InterruptedIOException e) {
// read interrupted
} catch (IOException e) {
e.printStackTrace();
}
});
th.start();
Thread.sleep(1000);
Thread.sleep(1000);
th.interrupt();
Thread.sleep(1000);
Thread.sleep(1000);
System.out.println(th.isAlive()); // Outputs false
}
但是等等! (另一个JavaAPI失败)
不幸的是,正如用户所指出的Motowski, there exists a "Won't Fix" bug in the Java API implementation of PipedInputSteam
. So if you use the unmodified library version of PipedInputSteam
as shown above, it will sometimes trigger a long sleep via wait(1000)
. To work around the bug, Developers must make their own FastPipedInputStream
subclass as described 。
我写了一个允许被中断的包装器 InputStream class:
package de.piegames.voicepi.stt;
import java.io.IOException;
import java.io.InputStream;
public class InterruptibleInputStream extends InputStream {
protected final InputStream in;
public InterruptibleInputStream(InputStream in) {
this.in = in;
}
/**
* This will read one byte, blocking if needed. If the thread is interrupted while reading, it will stop and throw
* an {@link IOException}.
*/
@Override
public int read() throws IOException {
while (!Thread.interrupted())
if (in.available() > 0)
return in.read();
else
Thread.yield();
throw new IOException("Thread interrupted while reading");
}
/**
* This will read multiple bytes into a buffer. While reading the first byte it will block and wait in an
* interruptable way until one is available. For the remaining bytes, it will stop reading when none are available
* anymore. If the thread is interrupted, it will return -1.
*/
@Override
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = -1;
while (!Thread.interrupted())
if (in.available() > 0) {
c = in.read();
break;
} else
Thread.yield();
if (c == -1) {
return -1;
}
b[off] = (byte) c;
int i = 1;
try {
for (; i < len; i++) {
c = -1;
if (in.available() > 0)
c = in.read();
if (c == -1) {
break;
}
b[off + i] = (byte) c;
}
} catch (IOException ee) {
}
return i;
}
@Override
public int available() throws IOException {
return in.available();
}
@Override
public void close() throws IOException {
in.close();
}
@Override
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
in.reset();
}
@Override
public boolean markSupported() {
return in.markSupported();
}
}
将 Thread.yield()
调整为您可以接受的最大延迟休眠时间,并为中断时的一些异常做好准备,但除此之外它应该可以正常工作。
如果我从 System.in
开始读取,它将阻塞线程直到它获取数据。没有办法阻止它。以下是我尝试过的所有方法:
- 中断线程
- 停止线程
- 关闭
System.in
- 调用
System.exit(0)
确实停止了线程,但它也杀死了我的应用程序,所以不理想。 - 在控制台中输入一个字符生成方法 return,但我不能依赖用户输入。
无效的示例代码:
public static void main(String[] args) throws InterruptedException {
Thread th = new Thread(() -> {
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
});
th.start();
Thread.sleep(1000);
System.in.close();
Thread.sleep(1000);
th.interrupt();
Thread.sleep(1000);
th.stop();
Thread.sleep(1000);
System.out.println(th.isAlive()); // Outputs true
}
当我运行这段代码时,它将永远输出true
和运行。
如何以可中断的方式从 System.in
读取?
您应该设计 运行 方法,使其能够自行确定何时终止。在线程上调用 stop() 或类似方法将是 inherently unsafe.
但是,如何避免内部阻塞的问题仍然存在System.in.read?为此,您可以在阅读之前轮询 System.in.available 直到 returns > 0。
示例代码:
Thread th = new Thread(() -> {
try {
while(System.in.available() < 1) {
Thread.sleep(200);
}
System.in.read();
} catch (InterruptedException e) {
// sleep interrupted
} catch (IOException e) {
e.printStackTrace();
}
});
当然,通常认为使用阻塞 IO 方法比轮询更有利。但是轮询确实有它的用处。在你的情况下,它允许这个线程干净地退出。
更好的方法:
避免轮询的 better approach 将重组代码,以便不允许您打算终止的任何线程直接访问 System.in
。这是因为 System.in 是一个不应关闭的 InputStream。相反,主线程或另一个专用线程将从 System.in (阻塞)中读取,然后将任何内容写入缓冲区。反过来,该缓冲区将由您打算杀死的线程监控。
示例代码:
public static void main(String[] args) throws InterruptedException, IOException {
PipedOutputStream stagingPipe = new PipedOutputStream();
PipedInputStream releasingPipe = new PipedInputStream(stagingPipe);
Thread stagingThread = new Thread(() -> {
try {
while(true) {
stagingPipe.write(System.in.read());
}
} catch (IOException e) {
e.printStackTrace();
}
});
stagingThread.setDaemon(true);
stagingThread.start();
Thread th = new Thread(() -> {
try {
releasingPipe.read();
} catch (InterruptedIOException e) {
// read interrupted
} catch (IOException e) {
e.printStackTrace();
}
});
th.start();
Thread.sleep(1000);
Thread.sleep(1000);
th.interrupt();
Thread.sleep(1000);
Thread.sleep(1000);
System.out.println(th.isAlive()); // Outputs false
}
但是等等! (另一个JavaAPI失败)
不幸的是,正如用户所指出的Motowski, there exists a "Won't Fix" bug in the Java API implementation of PipedInputSteam
. So if you use the unmodified library version of PipedInputSteam
as shown above, it will sometimes trigger a long sleep via wait(1000)
. To work around the bug, Developers must make their own FastPipedInputStream
subclass as described
我写了一个允许被中断的包装器 InputStream class:
package de.piegames.voicepi.stt;
import java.io.IOException;
import java.io.InputStream;
public class InterruptibleInputStream extends InputStream {
protected final InputStream in;
public InterruptibleInputStream(InputStream in) {
this.in = in;
}
/**
* This will read one byte, blocking if needed. If the thread is interrupted while reading, it will stop and throw
* an {@link IOException}.
*/
@Override
public int read() throws IOException {
while (!Thread.interrupted())
if (in.available() > 0)
return in.read();
else
Thread.yield();
throw new IOException("Thread interrupted while reading");
}
/**
* This will read multiple bytes into a buffer. While reading the first byte it will block and wait in an
* interruptable way until one is available. For the remaining bytes, it will stop reading when none are available
* anymore. If the thread is interrupted, it will return -1.
*/
@Override
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = -1;
while (!Thread.interrupted())
if (in.available() > 0) {
c = in.read();
break;
} else
Thread.yield();
if (c == -1) {
return -1;
}
b[off] = (byte) c;
int i = 1;
try {
for (; i < len; i++) {
c = -1;
if (in.available() > 0)
c = in.read();
if (c == -1) {
break;
}
b[off + i] = (byte) c;
}
} catch (IOException ee) {
}
return i;
}
@Override
public int available() throws IOException {
return in.available();
}
@Override
public void close() throws IOException {
in.close();
}
@Override
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
in.reset();
}
@Override
public boolean markSupported() {
return in.markSupported();
}
}
将 Thread.yield()
调整为您可以接受的最大延迟休眠时间,并为中断时的一些异常做好准备,但除此之外它应该可以正常工作。