プログラムを実行中に非同期にユーザからのキー操作を受けたいというような時は,I/Oのために別スレッドを立ち上げる. System.in を引数として InputStreamReader を作るのが簡単だが,スレッドの終わらせ方ではまってしまったのでメモしておく.
誤った実装の例
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の状況を監視し,入力があるときのみ読み込みを行うことで,メインスレッドからの割り込みを受け付けることが出来る.
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();
}
}
}
}
}
もっと良いやり方があれば教えてください.