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

多线程读取System.in时的重复输入问题求助

多线程读取System.in时的重复输入问题求助

老哥,你碰到的这个问题我之前也踩过坑,本质就是多线程抢占同一个System.in输入流导致的:两个线程各自实例化了BufferedReader来读标准输入,而System.in是全局唯一的流,底层共享同一个输入缓冲区。当method2触发异常关闭socket后,method1的线程可能已经卡在readLine()等着输入了,这时候你第一次敲的内容会被method1的reader先截走,第二次输入才会被method2的reader拿到,所以才需要按两次回车。

给你几个实用的解决方案,都是我实际用过的:

1. 共享全局输入读取器+同步锁(最推荐)

把BufferedReader改成类的成员变量,让所有需要读取输入的线程共享这一个实例,同时用synchronized加锁,保证同一时间只有一个线程能读取输入。从根源上避免抢占问题,代码改动也不大,符合你提到的“把reader作为类属性”的思路。

修改后的代码示例:

public class App {
    // 全局共享的输入读取器,只实例化一次
    private final BufferedReader sharedInputReader = new BufferedReader(new InputStreamReader(System.in));
    private Socket socket;

    public void method1() {
        new Thread(() -> {
            String line;
            try {
                while (!socket.isClosed()) {
                    // 加同步锁,确保同一时间只有一个线程能读取输入
                    synchronized (sharedInputReader) {
                        line = sharedInputReader.readLine();
                    }
                    if (line == null) break;
                    // 发送内容到Socket的逻辑
                }
            } catch (IOException e) {
                // 处理读取异常
                e.printStackTrace();
            }
        }).start();
    }

    public void method2() {
        try {
            // 持续接收Socket消息的逻辑
        } catch (Exception e) {
            try {
                socket.close();
                String userInput;
                // 同样用共享读取器,加锁读取
                synchronized (sharedInputReader) {
                    userInput = sharedInputReader.readLine();
                }
                // 询问用户是否连接其他服务器的逻辑
                System.out.println("是否连接其他服务器?输入Y确认:" + userInput);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

2. 用状态标记控制输入流向

加一个volatile修饰的状态变量,标记当前输入应该交给哪个线程处理。当method2触发异常后,把状态设为true,让method1的线程暂时跳过读取操作,直到method2处理完用户输入后再恢复状态。

示例代码:

public class App {
    private final BufferedReader sharedInputReader = new BufferedReader(new InputStreamReader(System.in));
    private Socket socket;
    // volatile保证状态在多线程间的可见性
    private volatile boolean isInputForReconnect = false;

    public void method1() {
        new Thread(() -> {
            String line;
            try {
                while (!socket.isClosed()) {
                    synchronized (sharedInputReader) {
                        // 如果当前输入要用于重连,就跳过读取
                        if (isInputForReconnect) {
                            continue;
                        }
                        line = sharedInputReader.readLine();
                    }
                    if (line == null) break;
                    // 发送到Socket的逻辑
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    public void method2() {
        try {
            // 接收Socket消息的逻辑
        } catch (Exception e) {
            try {
                socket.close();
                // 设置状态,让method1暂停读取输入
                isInputForReconnect = true;
                String userInput;
                synchronized (sharedInputReader) {
                    userInput = sharedInputReader.readLine();
                }
                // 处理重连逻辑
                System.out.println("收到重连指令:" + userInput);
                // 恢复状态,让method1继续读取
                isInputForReconnect = false;
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

3. 中断method1的线程

当method2触发异常关闭socket后,直接中断method1的线程,让它退出读取循环,这样就不会再抢占输入了。这种方式要注意正确处理线程中断,避免资源泄漏。

示例代码:

public class App {
    private Socket socket;
    // 保存method1的线程引用,方便后续中断
    private Thread inputSenderThread;

    public void method1() {
        inputSenderThread = new Thread(() -> {
            String line;
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
            try {
                // 同时检查socket状态和线程中断状态
                while (!socket.isClosed() && !Thread.currentThread().isInterrupted()) {
                    line = bufferedReader.readLine();
                    if (line == null) break;
                    // 发送到Socket的逻辑
                }
            } catch (IOException e) {
                // 线程被中断时会进入这里,做清理工作
                System.out.println("输入发送线程已停止");
            }
        });
        inputSenderThread.start();
    }

    public void method2() {
        try {
            // 接收Socket消息的逻辑
        } catch (Exception e) {
            try {
                socket.close();
                // 中断method1的线程,停止它读取输入
                if (inputSenderThread != null && inputSenderThread.isAlive()) {
                    inputSenderThread.interrupt();
                }
                String userInput;
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
                userInput = bufferedReader.readLine();
                // 询问用户是否连接其他服务器的逻辑
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

个人最推荐第一种方案,因为它逻辑简单,能从根本上解决多线程抢占输入流的问题,而且后续扩展也方便。

备注:内容来源于stack exchange,提问作者Kobra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 17:28:07