Java中能否中断无输入时等待控制台输入的线程?
兄弟,这个问题我之前踩过坑!Java里原生的控制台输入阻塞确实难搞,interrupt()、stop()都不管用,关System.in也得等输入结束,不过亲测有几个可行的办法,给你唠唠:
核心思路是给System.in包一层,在读取循环里检查线程的中断状态,一旦发现中断就抛出异常终止读取。这样就能让interrupt()生效了,代码示例如下:
import java.io.IOException; import java.io.InputStream; public class InterruptibleConsoleInputStream extends InputStream { private final InputStream originalIn; public InterruptibleConsoleInputStream(InputStream originalIn) { this.originalIn = originalIn; } @Override public int read() throws IOException { // 先检查一次中断状态 if (Thread.currentThread().isInterrupted()) { throw new IOException("读取线程已被中断"); } // 循环等待有输入可用,期间定期检查中断 while (originalIn.available() == 0) { if (Thread.currentThread().isInterrupted()) { throw new IOException("读取线程已被中断"); } try { // 短暂休眠,避免空循环占CPU Thread.sleep(100); } catch (InterruptedException e) { // 重置中断标志位,确保后续逻辑能感知到 Thread.currentThread().interrupt(); throw new IOException("读取线程已被中断"); } } return originalIn.read(); } // 记得重写read(byte[])等其他常用方法,逻辑和上面一致 @Override public int read(byte[] b) throws IOException { if (Thread.currentThread().isInterrupted()) { throw new IOException("读取线程已被中断"); } while (originalIn.available() == 0) { if (Thread.currentThread().isInterrupted()) { throw new IOException("读取线程已被中断"); } try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("读取线程已被中断"); } } return originalIn.read(b); } }
使用的时候,把System.in替换成这个包装类就行:
// 替换系统输入流 System.setIn(new InterruptibleConsoleInputStream(System.in)); // 启动读取线程 Thread readThread = new Thread(() -> { try { System.out.println("等待输入..."); String input = new java.util.Scanner(System.in).nextLine(); System.out.println("收到输入:" + input); } catch (IOException e) { System.out.println("读取被中断:" + e.getMessage()); } }); readThread.start(); // 模拟3秒后中断线程 try { Thread.sleep(3000); readThread.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); }
如果你的程序只跑在Unix或Linux上,可以通过给进程发送SIGINT信号(就是平时按Ctrl+C触发的信号)来中断输入。不过要先注册信号处理器,避免直接终止进程,示例代码:
import sun.misc.Signal; import sun.misc.SignalHandler; public class UnixConsoleInterrupt { public static void main(String[] args) { // 注册SIGINT信号处理器 Signal.handle(new Signal("INT"), new SignalHandler() { @Override public void handle(Signal signal) { System.out.println("\n收到中断信号,停止读取"); // 这里可以做清理工作,不用终止进程 } }); Thread readThread = new Thread(() -> { try { System.out.println("等待输入..."); // 原生读取,收到SIGINT会抛出IOException System.in.read(); } catch (IOException e) { System.out.println("读取线程退出:" + e.getMessage()); } }); readThread.start(); // 5秒后给自己发送SIGINT信号 try { Thread.sleep(5000); Runtime.getRuntime().exec("kill -SIGINT " + ProcessHandle.current().pid()); } catch (Exception e) { e.printStackTrace(); } } }
注意:这个方法Windows完全不适用,而且sun.misc.Signal是非标准API,可能在某些JDK版本里被移除,用的时候要谨慎。
如果不想自己写代码,可以用Apache Commons IO里的InterruptibleInputStream,它已经帮你实现了可中断的输入流逻辑,直接引入依赖后用就行:
import org.apache.commons.io.input.InterruptibleInputStream; // 包装System.in InputStream interruptibleIn = new InterruptibleInputStream(System.in); // 之后用这个流读取,调用线程interrupt()就会抛出IOException中断读取
不过这个需要你引入Apache Commons IO的依赖,适合已经用了这个库的项目。
简单说:System.in的底层是操作系统的文件描述符,原生的read()是阻塞在操作系统内核层面的,这时候线程处于阻塞状态,interrupt()只会设置线程的中断标志位,没法唤醒内核层面的阻塞;stop()早就被废弃了,而且对这种内核级阻塞也无能为力;关闭System.in的话,操作系统还是会等输入完成才会检测到流关闭,所以得等输入结束才生效。
内容的提问来源于stack exchange,提问作者Marnes

