不关闭InputStream中断BufferedReader#readLine()的实现难题
解决Process InputStream按需挂载/卸载(不关闭流)的问题
这个问题我之前也碰到过——直接关闭Process.getInputStream()绝对是禁区,一旦这么做,进程的输出管道会被切断,轻则后续无法再读取数据,重则直接导致进程挂掉。核心需求就是打破readLine()的阻塞状态,但保留流的可用性,下面给你几个实用的方案:
方案1:线程中断(最简单直接)
阻塞IO操作(比如readLine())在所在线程被中断时,会抛出InterruptedException,我们可以利用这一点来终止读取循环,同时流本身不会被关闭,后续还能重新启动读取线程。
代码示例
private Thread inputReaderThread; private BufferedReader reader; // 挂载:启动读取线程 public void mountInputStream() { if (inputReaderThread != null && inputReaderThread.isAlive()) { return; // 避免重复挂载 } // 复用Process的InputStream,不创建新的流 reader = new BufferedReader(new InputStreamReader(getProcess().getInputStream())); inputReaderThread = new Thread(() -> { String line; try { // 读取循环,直到线程被中断或流结束 while ((line = reader.readLine()) != null) { // 处理读取到的每一行数据 System.out.println("Process output: " + line); } } catch (InterruptedException e) { // 捕获中断异常,正常退出卸载流程 Thread.currentThread().interrupt(); // 保留中断状态(可选) System.out.println("InputStream读取线程已中断(卸载完成)"); } catch (IOException e) { // 处理其他IO异常,比如进程终止导致流关闭 e.printStackTrace(); } }); inputReaderThread.start(); } // 卸载:中断读取线程 public void unmountInputStream() { if (inputReaderThread == null || !inputReaderThread.isAlive()) { return; } inputReaderThread.interrupt(); try { inputReaderThread.join(); // 等待线程安全退出 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } inputReaderThread = null; }
注意点
- 中断线程只是打破阻塞的
readLine(),流本身完全保持打开状态,后续调用mountInputStream()可以直接复用。 - 要确保正确处理
InterruptedException,不要直接吞掉异常,否则线程可能无法正常退出。
方案2:队列缓冲(更健壮,适合高输出场景)
如果你的Process输出量很大,长时间不读取会导致进程的输出缓冲区满,进而让进程挂起。这种情况下,最好用一个后台线程持续读取Process的输出到队列,挂载/卸载只操作消费队列的线程——既保证进程不会卡住,又能灵活控制数据消费。
代码示例
import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; private BlockingQueue<String> outputQueue = new LinkedBlockingQueue<>(); private Thread processReaderThread; private Thread consumerThread; // 初始化:启动后台线程持续读取Process输出到队列 public void initProcessOutputReader() { processReaderThread = new Thread(() -> { BufferedReader reader = new BufferedReader(new InputStreamReader(getProcess().getInputStream())); String line; try { while ((line = reader.readLine()) != null) { outputQueue.put(line); // 阻塞式放入队列,避免溢出 } } catch (IOException | InterruptedException e) { // 进程终止或线程中断时退出 e.printStackTrace(); } }); processReaderThread.setDaemon(true); // 设置为守护线程,随主进程退出 processReaderThread.start(); } // 挂载:启动消费线程处理队列中的数据 public void mount() { if (consumerThread != null && consumerThread.isAlive()) { return; } consumerThread = new Thread(() -> { try { while (!Thread.currentThread().isInterrupted()) { String line = outputQueue.take(); // 阻塞等待队列数据 // 处理数据 System.out.println("Consumed output: " + line); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.out.println("消费线程已中断(卸载完成)"); } }); consumerThread.start(); } // 卸载:中断消费线程 public void unmount() { if (consumerThread == null || !consumerThread.isAlive()) { return; } consumerThread.interrupt(); try { consumerThread.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } consumerThread = null; // 可选:卸载时清空队列,避免下次挂载读到旧数据 // outputQueue.clear(); }
优势
- 彻底解决Process因输出缓冲区满而挂起的问题,这是很多人容易忽略的坑。
- 挂载/卸载完全和Process的流解耦,操作更安全灵活。
- 队列可以缓存未消费的数据,重新挂载时可以选择读取历史数据或清空。
方案3:自定义可中断流(复杂度较高,适合特殊场景)
如果你不想用线程中断,可以自定义一个代理流,加入中断标记,让读取方法在标记触发时返回-1,从而终止readLine()循环。不过这种方法要注意BufferedReader的缓冲区问题——如果缓冲区已有数据,会读完缓冲区才会返回。
代码示例(简化版)
public class InterruptibleInputStream extends InputStream { private final InputStream delegate; private volatile boolean isInterrupted = false; public InterruptibleInputStream(InputStream delegate) { this.delegate = delegate; } public void triggerInterrupt() { this.isInterrupted = true; } @Override public int read() throws IOException { if (isInterrupted) return -1; return delegate.read(); } @Override public int read(byte[] b, int off, int len) throws IOException { if (isInterrupted) return -1; return delegate.read(b, off, len); } // 其他方法(skip/available等)直接委托给delegate @Override public void close() throws IOException { // 注意:不要关闭原始的Process InputStream,这里可以留空或者按需处理 } }
然后使用这个流:
private InterruptibleInputStream interruptibleStream; private BufferedReader reader; private Thread readerThread; public void mount() { interruptibleStream = new InterruptibleInputStream(getProcess().getInputStream()); reader = new BufferedReader(new InputStreamReader(interruptibleStream)); readerThread = new Thread(() -> { String line; try { while ((line = reader.readLine()) != null) { // 处理数据 } } catch (IOException e) { e.printStackTrace(); } }); readerThread.start(); } public void unmount() { interruptibleStream.triggerInterrupt(); try { readerThread.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
总结
- 如果只是简单的挂载卸载,**方案1(线程中断)**足够用,代码最少,逻辑最简单。
- 如果你的Process输出量大,或者担心进程因缓冲区满挂起,**方案2(队列缓冲)**是更健壮的选择。
- 方案3适合对线程中断有顾虑的场景,但要处理BufferedReader缓冲区的问题,复杂度稍高。
内容的提问来源于stack exchange,提问作者Felix Gaebler
相关产品推荐
相关产品推荐

