Java Timer实现影院订票系统结账倒计时时终端输入无响应问题排查
问题原因分析
- 线程输入流冲突:Java的
Timer启动的TimerTask运行在独立的后台守护线程,你在该线程内新建Scanner读取全局唯一的System.in输入流,而主线程的checkout()方法大概率也在使用Scanner监听用户输入,两个线程抢占输入资源后,你在终端输入的内容会被其中一个线程吃掉,Timer线程的inputObj.next()拿不到输入就会一直阻塞,所以不会执行后续的打印语句。 - 控制台输出混乱:5分钟超时后Timer线程突然打印过期提示,和主线程的结账流程输出互相穿插,也会导致用户交互逻辑错乱。
- 额外逻辑错误:你现有代码里不管结账成功失败都对
numSeatsAvailable执行+1操作,结账成功占用座位的场景下可用座位数应该减少,这里逻辑写反了。
修复方案
核心原则:所有用户IO操作全部放在主线程执行,Timer后台线程仅负责标记超时状态,不要跨线程操作输入输出流。
步骤1:定义线程共享的超时标记
// 多线程共享的超时标记,加volatile保证多线程下的状态可见性 private volatile boolean isCheckoutTimeout = false;
步骤2:调整expiredCheckout逻辑
该方法改为仅在主线程调用,不用修改内部核心交互逻辑:
private void expiredCheckout() { Scanner inputObj = new Scanner(System.in); while (true) { System.out.println("Your session has expired!"); System.out.println("If you want to start over, press 1"); System.out.println("If you want to cancel, press q"); String input = inputObj.next(); System.out.println("made it here"); if (input.equals("q")) return; else if (input.equals("1")) { this.runTheatreUI(); break; } else { System.out.println("Please enter valid choice!"); } } }
步骤3:重写startTimer逻辑
Timer后台线程仅负责标记超时,不要处理用户输入:
private void startTimer(ArrayList<Seat> seatsSelected) { // 每次启动计时器先重置超时标记 isCheckoutTimeout = false; Timer timer = new Timer(); timer.schedule( new TimerTask() { public void run() { // 后台线程仅做超时标记和基础提示,不读取输入 isCheckoutTimeout = true; System.out.println("\nYour checkout session has timed out."); } } ,5*60*1000); ArrayList<Object> checkoutInfo = this.checkout(); timer.cancel(); // 先处理超时场景 if (isCheckoutTimeout) { expiredCheckout(); // 超时默认释放选中座位 for (Seat seat : seatsSelected) { seat.setStatus(Seat.OPEN); } this.numSeatsAvailable += seatsSelected.size(); return; } // 处理正常结账场景 if (checkoutInfo.get(0).equals(true)) { for (Seat seat : seatsSelected) { seat.setStatus(Seat.TAKEN); } // 结账成功,可用座位数减少 this.numSeatsAvailable -= seatsSelected.size(); } else { // 用户主动取消,释放座位 for (Seat seat : seatsSelected) { seat.setStatus(Seat.OPEN); } this.numSeatsAvailable += seatsSelected.size(); } }
步骤4:修改checkout方法适配超时逻辑
你需要在原来的checkout方法中加入超时状态轮询,避免一直阻塞等待输入:
// 示例:checkout内的输入读取逻辑调整为非阻塞轮询 private ArrayList<Object> checkout() { ArrayList<Object> result = new ArrayList<>(); Scanner scanner = new Scanner(System.in); try { while (!isCheckoutTimeout) { // 检测是否有用户输入 if (System.in.available() > 0) { String input = scanner.next(); // 原有结账输入处理逻辑,处理完成后设置result返回 break; } // 降低轮询CPU占用 Thread.sleep(100); } } catch (IOException | InterruptedException e) { // 异常处理逻辑 result.add(false); } return result; }
内容的提问来源于stack exchange,提问作者dvr
相关产品推荐
相关产品推荐

