一、背景

写个程序我需要一种场景,在程序启动时在控制台进行主动询问。如果输入为'y'则执行逻辑A,如果输入为非'y'或当等待输入时间大于3秒时执行逻辑B。

二、问题分析

采用System.in.read来截获键盘输入比较常见,但想做到自动输入超时,好像并没有原生提供该功能。我想到应该用Thread来解决,在一个新的Thread中输入,主Thread进行等待。

但后台来现如果Thread被中断,Thread中的System.in.read并不会被终止。所以改为Channles的方式读取控制台输入,如果Thread被中断read会抛出异常来彻底结束该Thread。

三、程序代码

代码中有注释,这里就不细说了。

package org.noahx.inreadtimeout;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ClosedByInterruptException;
/**
* Created with IntelliJ IDEA.
* User: noah
* Date: 8/26/13
* Time: 10:37 PM
* To change this template use File | Settings | File Templates.
*/
public class TestTimeout {
public static void main(String[] args) {
final TestTimeout testTimeout = new TestTimeout();
boolean doClean = testTimeout.readInput();   //等待输入3秒,超时按false处理
if (doClean) {
//cleanDb();
System.out.println("The database was cleaned!");
} else {
System.out.println("The clean operation was ignored.");
}
}
public boolean readInput() {
System.out.println("Do you want to clean and initialize the database?(y/n)");
final boolean[] doClean = {false};  //用数组接收返回值
Thread inThread = new Thread() {
@Override
public void run() {
// 不采用System.in是因为,该线程被终止时System.in无法终止
InputStream stdInCh = Channels.newInputStream((
new FileInputStream(FileDescriptor.in)).getChannel());     // 采用Channels的InputStream,同样的输入效果,但会触发ClosedByInterruptException异常
try {
switch ((char) stdInCh.read()) {  //读取按键
case 'y':
case 'Y': {
doClean[0] = true;  //设置返回为true
break;
}
}
synchronized (TestTimeout.this) {
TestTimeout.this.notify();   //通知主线程停止等待
}
} catch (ClosedByInterruptException e) {  //调用inThread.interrupt()方法,会触发该异常
//忽略中断异常
} catch (IOException e) {
e.printStackTrace();
}
}
};
inThread.start();
try {
synchronized (this) {
this.wait(3000);   //等待3秒,可以修改为更长的时间来观查效果,如6000
}
if (inThread.isAlive()) {  //如果inThread还活着说明,是3000超时导致
inThread.interrupt();   //强行终止inThread
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return doClean[0];
}
}

四、执行效果

1、3秒内输入y

程序选择true逻辑

Do you want to clean and initialize the database?(y/n)

y

The database was cleaned!

2、3秒不输入任何内容或输入y以外字符

程序选择false逻辑

Do you want to clean and initialize the database?(y/n)

The clean operation was ignored

五、总结

这种方式不光可以应用在纯Java Application上,也可以写在Spring的Bean(implements InitializingBean)与Web容器中。实际项目中我就是这样应用的,在afterPropertiesSet中加入询问是否需要初始化数据库代码,来完成快速初始化数据(不修改配置文件)。

P.S.

我的使用场景

采用询问方式初始化数据库的好处是,方便开发调试(IDE都有控制台)。提示时输入一个y字符,库表就会被全部清空,hibernate调用create建表(正常validate),再自动调用导入程序导入初始数据。提示时如果不理睬(不会初始化数据库)3秒后程序继续正常执行。