首先:这不会解决如果有未完成的输入请求(即使已取消),关闭整个程序需要 System.exit() 调用的问题。您可以潜在地通过在控制台中欺骗击键来规避这一点,但那完全是另一个球场。
如果你想在控制台中进行,没有轮询是不可能的,因为实际上不可能解除阻塞等待 System.in 输入的线程,因为 System.in 本身没有可中断的 get() 方法.因此,如果您知道它不会阻塞,则无需使用轮询来请求输入。
如果你真的想要一个可以作为控制台可中断的 nextLine() 的东西,你可能应该考虑制作一个 Swing 窗口或类似的东西,并为它制作一个简单的输入界面。这并不难,除了一些边缘情况外,它还具有您要求的所有功能。
但是,我自己也在做这个,因为我想要一种方法让线程停止等待来自 System.in 的输入,而无需关闭程序(同时避免轮询),这就是我想出的,在切换到我自己的窗口之前。
我不能肯定地说这是最佳实践,但它应该是线程安全的,似乎工作正常,而且我想不出任何直接的问题。我想将故障从备用(尽管否则无法获得)输出切换到实际错误。您可以通过中断线程或调用 cancel() 来取消活动的输入请求,这会取消当前等待的请求。
它使用信号量和线程来创建一个阻塞的 nextLine() 方法,该方法可以在其他地方被中断/取消。取消并不完美 - 例如,您只能取消当前等待线程的请求,但中断线程应该可以正常工作。
package testapp;
/**
*
* @author Devlin Grasley
*/
import java.util.concurrent.Semaphore;
import java.util.Scanner;
public class InterruptableSysIn {
protected static Scanner input = new Scanner (System.in);
protected static final Semaphore waitingForInput = new Semaphore(0,true); //If InterruptableSysIn is waiting on input.nextLine(); Can also be cleared by cancel();
protected static String currentLine = ""; //What the last scanned-in line is
private static final Input inputObject = new Input();
private static final Semaphore waitingOnOutput = new Semaphore (1); // If there's someone waiting for output. Used for thread safety
private static boolean canceled = false; //If the last input request was cancled.
private static boolean ignoreNextLine = false; //If the last cancel() call indicated input should skip the next line.
private static final String INTERRUPTED_ERROR = "\nInterrupted";
private static final String INUSE_ERROR = "\nInUse";
private static boolean lasLineInterrupted = false;
/**
* This method will block if someone else is already waiting on a next line.
* Gaurentees on fifo order - threads are paused, and enter a queue if the
* input is in use at the time of request, and will return in the order the
* requests were made
* @return The next line from System.in, or "\nInterrupted" if it's interrupted for any reason
*/
public static String nextLineBlocking(){
//Blocking portion
try{
waitingOnOutput.acquire(1);
}catch(InterruptedException iE){
return INTERRUPTED_ERROR;
}
String toReturn = getNextLine();
waitingOnOutput.release(1);
return toReturn;
}
/**
* This method will immediately return if someone else is already waiting on a next line.
* @return The next line from System.in, or
* "\nInterrupted" if it's interrupted for any reason
* "\nInUse" if the scanner is already in use
*/
public static String nextLineNonBlocking(){
//Failing-out portion
if(!waitingOnOutput.tryAcquire(1)){
return INUSE_ERROR;
}
String toReturn = getNextLine();
waitingOnOutput.release(1);
return toReturn;
}
/**
* This method will block if someone else is already waiting on a next line.
* Gaurentees on fifo order - threads are paused, and enter a queue if the
* input is in use at the time of request, and will return in the order the
* requests were made
* @param ignoreLastLineIfUnused If the last line was canceled or Interrupted, throw out that line, and wait for a new one.
* @return The next line from System.in, or "\nInterrupted" if it's interrupted for any reason
*/
public static String nextLineBlocking(boolean ignoreLastLineIfUnused){
ignoreNextLine = ignoreLastLineIfUnused;
return nextLineBlocking();
}
/**
* This method will fail if someone else is already waiting on a next line.
* @param ignoreLastLineIfUnused If the last line was canceled or Interrupted, throw out that line, and wait for a new one.
* @return The next line from System.in, or
* "\nInterrupted" if it's interrupted for any reason
* "\nInUse" if the scanner is already in use
*/
public static String nextLineNonBlocking(boolean ignoreLastLineIfUnused){
ignoreNextLine = ignoreLastLineIfUnused;
return nextLineNonBlocking();
}
private static String getNextLine(){
String toReturn = currentLine; //Cache the current line on the very off chance that some other code will run etween the next few lines
if(canceled){//If the last one was cancled
canceled = false;
//If there has not been a new line since the cancelation
if (toReturn.equalsIgnoreCase(INTERRUPTED_ERROR)){
//If the last request was cancled, and has not yet recieved an input
//wait for that input to finish
toReturn = waitForLineToFinish();
//If the request to finish the last line was interrupted
if(toReturn.equalsIgnoreCase(INTERRUPTED_ERROR)){
return INTERRUPTED_ERROR;
}
if(ignoreNextLine){
//If the last line is supposed to be thrown out, get a new one
ignoreNextLine = false;
//Request an input
toReturn = getLine();
}else{
return toReturn;
}
//If there has been a new line since cancelation
}else{
//If the last request was cancled, and has since recieved an input
try{
waitingForInput.acquire(1); //Remove the spare semaphore generated by having both cancel() and having input
}catch(InterruptedException iE){
return INTERRUPTED_ERROR;
}
if(ignoreNextLine){
ignoreNextLine = false;
//Request an input
toReturn = getLine();
}
//return the last input
return toReturn;
}
}else{
if(lasLineInterrupted){
//wait for that input to finish
toReturn = waitForLineToFinish();
//If the request to finish the last line was interrupted
if(toReturn.equalsIgnoreCase(INTERRUPTED_ERROR)){
return INTERRUPTED_ERROR;
}
//Should the read be thrown out?
if(ignoreNextLine){
//Request an input
toReturn = getLine();
}
}else{
ignoreNextLine = false; //If it's been set to true, but there's been no cancaleation, reset it.
//If the last request was not cancled, and has not yet recieved an input
//Request an input
toReturn = getLine();
}
}
return toReturn;
}
private static String getLine (){
Thread ct = new Thread(inputObject);
ct.start();
//Makes this cancelable
try{
waitingForInput.acquire(1); //Wait for the input
}catch(InterruptedException iE){
lasLineInterrupted = true;
return INTERRUPTED_ERROR;
}
if(canceled){
return INTERRUPTED_ERROR;
}
return currentLine;
}
public static String waitForLineToFinish(){
//If the last request was interrupted
//wait for the input to finish
try{
waitingForInput.acquire(1);
lasLineInterrupted = false;
canceled = false;
return currentLine;
}catch(InterruptedException iE){
lasLineInterrupted = true;
return INTERRUPTED_ERROR;
}
}
/**
* Cancels the currently waiting input request
*/
public static void cancel(){
if(!waitingOnOutput.tryAcquire(1)){ //If there is someone waiting on user input
canceled = true;
currentLine = INTERRUPTED_ERROR;
waitingForInput.release(1); //Let the blocked scanning threads continue, or restore the lock from tryAquire()
}else{
waitingOnOutput.release(1); //release the lock from tryAquire()
}
}
public static void cancel(boolean throwOutNextLine){
if(!waitingOnOutput.tryAcquire(1)){ //If there is someone waiting on user input
canceled = true;
currentLine = INTERRUPTED_ERROR;
ignoreNextLine = throwOutNextLine;
waitingForInput.release(1); //Let the blocked scanning threads continue
}else{
waitingOnOutput.release(1); //release the lock from tryAquire()
}
}
}
class Input implements Runnable{
@Override
public void run (){
InterruptableSysIn.currentLine = InterruptableSysIn.input.nextLine();
InterruptableSysIn.waitingForInput.release(1); //Let the main thread know input's been read
}
}