標準入力の処理を別スレッドで行うときのI/Oスレッドの終わらせ方

プログラムを実行中に非同期にユーザからのキー操作を受けたいというような時は，I/Oのために別スレッドを立ち上げる． _System.in_ を引数として _InputStreamReader_ を作るのが簡単だが，スレッドの終わらせ方ではまってしまったのでメモしておく．

### 誤った実装の例

``` maltithread_io_wrong.java
public class MainClass {
    public void func() {
        Thread commandReceiver = new Thread(new CommandReceiver());
        commandReceiver.start();
        // do something
        commandReceiver.interrupt();
    }

    // I/O処理用スレッド
    private class CommandReceiver implements Runnable {
        @Override
        public void run() {
            BufferedReader reader 
                = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                try {
// --- 下の処理でブロックしてしまうので，このようなプログラムを書いてはいけない ---
                    String command = reader.readLine(); 
                    // do something based on command
                } catch (IOException e) {
                    break;
                }
            }
        }
    }
}
```

ここでは，ユーザの入力を受け取るために，CommandReceiverというクラスを実装している．
CommandReceiverは，

```
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
```

としてreaderを生成し，

```
reader.readLine();
```
によってユーザ入力を受け取っている．

メインスレッドの処理が終わると，

```
commandReceiver.interrupt();
```
をコールし，[InterruptedIOException](http://docs.oracle.com/javase/jp/6/api/java/io/InterruptedIOException.html)が呼び出されることを期待するが，実際にはI/O用スレッドはinputStreamからの入力を待つブロックの状態のままである．
参考: [ブロッキングI/Oに対してThread.interrupt()をコールしても，一部SolarisOS以外ではInterruptedIOExceptionは発生しない](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4385444).

実際，[Thraed#interruptのJavaDoc](http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/Thread.html#interrupt(\))を参照すると，割り込みが発生する場合は以下の様になっている．

* Objectクラスのwait(), wait(long), wait(long, int)でブロック中のとき，またはThreadクラスのjoin(), join(long), join(long, int), sleep(long), sleep(long, int)でブロック中のとき
　→ InterruptedExceptionが発生
* java.nio.channelsのInterruptibleChannelによるブロック中のとき
　→ チャネルが閉じられClosedByInterruptExceptionが発生
* java.nio.channelsのSelectorによるブロック中のとき
　→ selection操作が直ちに終了

_System.in_ に代表されるStreamはブロッキングI/Oを想定しており，簡単に割り込みに対応することは出来なさそうだ．


### 上記問題の回避策

単純なワークアラウンドは以下のようになる．Thread.sleep()を用いて定期的にBufferedReaderの状況を監視し，入力があるときのみ読み込みを行うことで，メインスレッドからの割り込みを受け付けることが出来る．

``` maltitherad_io_with_sleep.java
public class MainClass {
    public void func() {
        Thread commandReceiver = new Thread(new CommandReceiver());
        commandReceiver.start();
        // do something
        commandReceiver.interrupt();
    }

    private class CommandReceiver implements Runnable {
        @Override
        public void run() {
            BufferedReader reader 
                = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                try {
                    while (!reader.ready()) {
                        // ここで割り込みを待つ
                        Thread.sleep(200);
                    }
                    String command = reader.readLine();
                    // do something based on command
                } catch (InterruptedException e) {
                    break;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
```

もっと良いやり方があれば教えてください．