多线程读取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
相关产品推荐
相关产品推荐

