LoginSignup
12
11

More than 5 years have passed since last update.

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

Posted at

プログラムを実行中に非同期にユーザからのキー操作を受けたいというような時は,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が呼び出されることを期待するが,実際にはI/O用スレッドはinputStreamからの入力を待つブロックの状態のままである.
参考: ブロッキングI/Oに対してThread.interrupt()をコールしても,一部SolarisOS以外ではInterruptedIOExceptionは発生しない.

実際,[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();
                }
            }
        }
    }
}

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

12
11
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
11