程式扎記: [ Java 文章收集 ] Timeout on Console Input

標籤

2015年2月8日 星期日

[ Java 文章收集 ] Timeout on Console Input

Source From Here
Preface
The problem that Maximilian was trying to solve was to have a simple console based application that would give users a certain time to answer the question. It would then timeout and retry a few times. The reason this was a difficult problem to solve was that System.in blocks on the readLine() method. The thread state is RUNNABLE when you are blocking on IO, not WAITING or TIMED_WAITING. It therefore does not respond to interruptions. Here is some sample code that shows the thread state of the Thread:
  1. import java.io.*;  
  2.   
  3. public class ReadLineTest {  
  4.   public static void main(String[] args) throws IOException {  
  5.     BufferedReader in = new BufferedReader(  
  6.         new InputStreamReader(System.in)  
  7.     );  
  8.     in.readLine();  
  9.   }  
  10. }  
The thread dump clearly shows the state (Using JConsole to dump Thread information):


Since blocking reads cannot be interrupted with Thread.interrupt(), we traditionally stop them by closing the underlying stream. From tutorial of "Futures and Callables", one of the working examples is how to write a non-blocking server using Java NIO. (Told you it was a comprehensive course :-)) However, sinceSystem.in is a traditional stream, we cannot use non-blocking techniques. Also, we cannot close it, since that would close it for all readers.

One little method in the BufferedReader will be able to help us. We can call BufferedReader.ready()which will only return true if the readLine() method can be called without blocking. This implies that the stream not only contains data, but also a newline character.

The first problem is therefore solved. However, if we read the input in a thread, we still need to find a way to get the String input back to the calling thread. TheExecutorService in Java 5 will work well here. We can implement Callable and return the String that was read. Unfortunately we need to poll until something has been entered. Currently we sleep for 200 milliseconds between checks, but we could probably make that much shorter if we want instant response. Since we are sleeping, thus putting the thread in the TIMED_WAITING state, we can interrupt this task at any time. One last catch was that we do not want to accept an empty line as a valid input.
  1. import java.io.*;  
  2. import java.util.concurrent.Callable;  
  3.   
  4. public class ConsoleInputReadTask implements Callable {  
  5.   public String call() throws IOException {  
  6.     BufferedReader br = new BufferedReader(  
  7.         new InputStreamReader(System.in));  
  8.     System.out.println("ConsoleInputReadTask run() called.");  
  9.     String input;  
  10.     do {  
  11.       System.out.println("Please type something: ");  
  12.       try {  
  13.         // wait until we have data to complete a readLine()  
  14.         while (!br.ready()) {  
  15.           Thread.sleep(200);  
  16.         }  
  17.         input = br.readLine();  
  18.       } catch (InterruptedException e) {  
  19.         System.out.println("ConsoleInputReadTask() cancelled");  
  20.         return null;  
  21.       }  
  22.     } while ("".equals(input));  
  23.     System.out.println("Thank You for providing input!");  
  24.     return input;  
  25.   }  
  26. }  

The next task is to call the ConsoleInputReadTask and timeout after some time. We do that by calling get() on the Future that is returned by the submit()method on ExecutorService.
  1. import java.util.concurrent.*;  
  2.   
  3. public class ConsoleInput {  
  4.   private final int tries;  
  5.   private final int timeout;  
  6.   private final TimeUnit unit;  
  7.   
  8.   public ConsoleInput(int tries, int timeout, TimeUnit unit) {  
  9.     this.tries = tries;  
  10.     this.timeout = timeout;  
  11.     this.unit = unit;  
  12.   }  
  13.   
  14.   public String readLine() throws InterruptedException {  
  15.     ExecutorService ex = Executors.newSingleThreadExecutor();  
  16.     String input = null;  
  17.     try {  
  18.       // start working  
  19.       for (int i = 0; i < tries; i++) {  
  20.         System.out.println(String.valueOf(i + 1) + ". loop");  
  21.         Future result = ex.submit(  
  22.             new ConsoleInputReadTask());  
  23.         try {  
  24.           input = result.get(timeout, unit);  
  25.           break;  
  26.         } catch (ExecutionException e) {  
  27.           e.getCause().printStackTrace();  
  28.         } catch (TimeoutException e) {  
  29.           System.out.println("Cancelling reading task");  
  30.           result.cancel(true);  
  31.           System.out.println("\nThread cancelled. input is null");  
  32.         }  
  33.       }  
  34.     } finally {  
  35.       ex.shutdownNow();  
  36.     }  
  37.     return input;  
  38.   }  
  39. }  
We can put all this to the test with a little test class. It takes the number of tries and the timeout in seconds from the command line and instantiates thatConsoleInput class, reading from it and displaying the String:
  1. import java.util.concurrent.TimeUnit;  
  2.   
  3. public class ConsoleInputTest {  
  4.   public static void main(String[] args)  
  5.       throws InterruptedException {  
  6.     if (args.length != 2) {  
  7.       System.out.println(  
  8.           "Usage: java ConsoleInputTest " +  
  9.               "");  
  10.       System.exit(0);  
  11.     }  
  12.   
  13.     ConsoleInput con = new ConsoleInput(  
  14.         Integer.parseInt(args[0]),  
  15.         Integer.parseInt(args[1]),  
  16.         TimeUnit.SECONDS  
  17.     );  
  18.   
  19.     String input = con.readLine();  
  20.     System.out.println("Done. Your input was: " + input);  
  21.   }  
  22. }  
This seems to satisfy all the requirements that we were trying to fulfill. To be honest, when I first saw the problem, I did not think it could be done. There is at least one way you could potentially get this program to fail. If you call the ConsoleInput.readLine() method from more than one thread, you run the very real risk of a data race between the ready() and readLine() methods. You would then block on the BufferedReader.readLine() method, thus potentially never completing.

Supplement
Oracle Doc - Using JConsole
The JConsole graphical user interface is a monitoring tool that complies to the Java Management Extensions (JMX) specification. JConsole uses the extensive instrumentation of the Java Virtual Machine (Java VM) to provide information about the performance and resource consumption of applications running on the Java platform...

Under previous releases of the Java SE platform, applications that you wanted to monitor with JConsole needed to be started with the following option.
% -Dcom.sun.management.jmxremote

How to Analyze Java Thread Dumps
In order to analyze a thread dump, you need to know the status of threads. The statuses of threads are stated on java.lang.Thread.State.

* NEW: The thread is created but has not been processed yet.
* RUNNABLE: The thread is occupying the CPU and processing a task. (It may be in WAITING status due to the OS's resource distribution.)
* BLOCKED: The thread is waiting for a different thread to release its lock in order to get the monitor lock.
* WAITING: The thread is waiting by using a wait, join or park method.
* TIMED_WAITING: The thread is waiting by using a sleep, wait, join or park method. (The difference from WAITING is that the maximum waiting time is specified by the method parameter, and WAITING can be relieved by time as well as external changes.)

How do I generate a Java thread dump on Linux/Unix?
Is it possible to read from a InputStream with a timeout?


沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!