如何中断对System.in的读取?线程阻塞问题求解
解决System.in读取时线程无法停止的问题
哥们,我太懂你这个痛点了——当线程卡在System.in的读取操作上时,常规的线程中断、甚至关闭System.in都没法让阻塞的read()方法返回,总不能每次都靠System.exit(0)终止整个应用,或者依赖用户输入来救场吧?
先给你说下为什么常规方法没用:System.in对应的InputStream是阻塞式的,它的read()方法不会响应线程中断,而且在某些JVM实现里,直接关闭System.in也不会让阻塞的读取操作立刻返回,反而可能抛出异常但没法正常退出线程。
下面给你两个靠谱的解决方案,亲测有效:
方案一:自定义可中断的InputStream
我们可以包装原生的System.in,自己实现一个能响应关闭信号和线程中断的输入流,让读取操作不再无限阻塞。
自定义流代码
import java.io.IOException; import java.io.InputStream; public class InterruptibleConsoleInput extends InputStream { private final InputStream originalInput; private volatile boolean isStopped = false; public InterruptibleConsoleInput(InputStream inputStream) { this.originalInput = inputStream; } // 主动停止读取的方法 public void stopReading() { isStopped = true; try { originalInput.close(); } catch (IOException e) { // 这里可以根据需求处理异常,或者直接忽略 } } @Override public int read() throws IOException { if (isStopped) { return -1; // 返回-1表示流结束,触发读取循环退出 } // 先检查是否有可用数据,避免直接阻塞 if (available() > 0) { return originalInput.read(); } // 没有数据时短暂休眠,期间可以响应中断或关闭信号 try { Thread.sleep(100); } catch (InterruptedException e) { // 重新设置线程中断状态,让上层逻辑可以感知 Thread.currentThread().interrupt(); return -1; } // 递归检查,直到有数据或者收到停止信号 return isStopped ? -1 : read(); } @Override public int available() throws IOException { return originalInput.available(); } @Override public void close() throws IOException { stopReading(); } }
使用示例
public static void main(String[] args) throws InterruptedException { InterruptibleConsoleInput interruptibleInput = new InterruptibleConsoleInput(System.in); Thread readerThread = new Thread(() -> { int data; try { while ((data = interruptibleInput.read()) != -1) { System.out.println("读取到输入:" + (char) data); } System.out.println("读取线程已正常退出"); } catch (IOException e) { e.printStackTrace(); } }); readerThread.start(); // 模拟程序运行5秒后主动停止读取线程 Thread.sleep(5000); System.out.println("准备停止读取操作..."); interruptibleInput.stopReading(); readerThread.join(); System.out.println("主线程完成"); }
这个方案的核心是:让读取操作不再一直阻塞,而是每隔100毫秒醒来检查一次是否收到停止信号,同时也能响应线程中断,完美解决了无限阻塞的问题。
方案二:使用NIO的Selector实现非阻塞读取
如果更倾向于用Java NIO的方式,我们可以把System.in转换成ReadableByteChannel,配合Selector来实现可控制的读取,避免无限阻塞。
NIO实现代码
import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.channels.Selector; import java.nio.channels.SelectionKey; import java.util.Iterator; public class NioConsoleReader { public static void main(String[] args) throws IOException, InterruptedException { ReadableByteChannel inputChannel = Channels.newChannel(System.in); Selector selector = Selector.open(); // 注册读事件到Selector inputChannel.register(selector, SelectionKey.OP_READ); Thread readerThread = new Thread(() -> { try { while (selector.isOpen()) { // 设置100毫秒超时,避免一直阻塞在select上 if (selector.select(100) == 0) { continue; } Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isReadable()) { ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = inputChannel.read(buffer); if (bytesRead == -1) { // 流结束,关闭Selector退出循环 selector.close(); break; } buffer.flip(); System.out.println("读取到输入:" + new String(buffer.array(), 0, bytesRead).trim()); } keyIterator.remove(); } } inputChannel.close(); System.out.println("读取线程已正常退出"); } catch (IOException e) { e.printStackTrace(); } }); readerThread.start(); // 模拟运行5秒后停止读取 Thread.sleep(5000); System.out.println("准备停止读取操作..."); selector.close(); // 关闭Selector会让select方法退出,从而终止读取循环 readerThread.join(); System.out.println("主线程完成"); } }
这个方案的原理是:通过Selector的超时机制,让线程不会一直阻塞在读取操作上,每隔100毫秒就会醒来一次,当需要停止时,只需要关闭Selector,线程就能正常退出。
总结
不管用哪种方案,核心思路都是打破System.in.read()的无限阻塞状态,让线程有机会检查停止信号或者响应中断。两种方案都不需要依赖用户输入,也不会终止整个应用,完美解决你的问题。
内容的提问来源于stack exchange,提问作者piegames
相关产品推荐
相关产品推荐

