1

I have a service which performs a reading from standard input. Here it is

public class ReaderService{
    private BufferedReader reader;
    private volatile boolean closed;

    public void start(){
        while(!closed){
            reader = new BufferedReader(new InputStreamReader(System.in));
            try {
                System.out.println(reader.readLine());
            } catch (IOException e) {
                System.out.println("IOException");
            }
        }
    }

    public void stop(){
        try {
            reader.close();
        } catch (IOException e) {
            System.exit(1);
        }
        closed = true;
    }
}

I use it as follows:

public static void main(String[] args) throws InterruptedException{
    Thread t = new Thread(new Runnable() {
        public void run() {
            reader.start();
        }
    });
    t.start();
    Thread.sleep(5000);
    reader.stop();
}

The issue is that calling ReaderService::stop() does not interrupt wainting for the next line. I thought it would throw IOExecption.

Is there a way to "interrupt" such a service from another thread? Maybe BufferedReader is not quite a good choice here...

stella
  • 2,546
  • 2
  • 18
  • 33

2 Answers2

2

Your expectation is right, if the main thread had an exclusive access to the stream that it eventually tries to close. But that is not the case here and that's why this program demonstrates the wait. When you press an enter (which is when the line buffered stream like System.in flushes the contents), the program exits as expected.

To illustrate, do the following:

  1. Rename your reader in the main method to service.
  2. Run your program in a terminal.
  3. While the program is waiting for you to press an Enter, in another terminal: jps -v -- find the JVM process (p) that runs ReaderService and then do a jstack <p>. This brings you a Java Thread Dump.

You should get something like (other threads omitted for brevity):

"main" #1 prio=5 os_prio=31 tid=0x00007fef4d803000 nid=0xf07 
 waiting for monitor entry [0x000000010b7a3000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at java.io.BufferedReader.close(BufferedReader.java:522)
    - waiting to lock <0x000000076ac47f78> (a java.io.InputStreamReader)
    at ReaderService.stop(ReaderService.java:19)
    at ReaderService.main(ReaderService.java:34)

"Thread-0" #10 prio=5 os_prio=31 tid=0x00007fef4c873800 nid=0x5503 runnable [0x000000012b497000]
   java.lang.Thread.State: RUNNABLE
    at java.io.FileInputStream.readBytes(Native Method)
    at java.io.FileInputStream.read(FileInputStream.java:255)
    at java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
    at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
    - locked <0x000000076ab1bf10> (a java.io.BufferedInputStream)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    - locked <0x000000076ac47f78> (a java.io.InputStreamReader)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    - locked <0x000000076ac47f78> (a java.io.InputStreamReader)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at ReaderService.start(ReaderService.java:10)
    at ReaderService$1.run(ReaderService.java:29)
    at java.lang.Thread.run(Thread.java:745)

As you can see, after the main thread is done sleeping, it goes into a BLOCKED state waiting to acquire the lock on the BufferedReader that represents a mutable shared state (which in this case main thread shares with the thread t). As expected, the thread t has already locked the BufferedReader's lock 0x000000076ac47f78 and has entered the critical section. Note that t is in RUNNABLE state, only waiting for someone to press Enter. Once that happens, the things should return to normal since eventually, main thread should succeed in acquiring the lock as in BufferedReader.java source code (around line 522 in JDK 1.8):

public void close() throws IOException {
    synchronized (lock) {
        if (in == null)
            return;
        try {
            in.close();
        } finally {
            in = null;
            cb = null;
        }
    }
}

You'll also see some interesting behavior if you do:

  1. On a Unix terminal, create a text file x with some text in it.
  2. Run java ReaderService < x.

Perhaps that behavior is closer to your expectation. But it has to do with how I/O buffering works for various types of streams.

Kedar Mhaswade
  • 4,535
  • 2
  • 25
  • 34
2

Check reader.ready() before reading. If it's false, then check the closed flag and sleep for 100ms or so.

It's not ideal, but I think it's the best way to timeout on System.in. This is only appropriate for System.in, though -- if you're serving a socket then you should use a real timeout.

Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87