You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何中断对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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:33:27