ユーザインタフェースを作る際、よく使うマルチスレッドの技
画面のイベント処理、たとえば、ボタンを押したときの処理に時間がかかる場合、ボタンを押してから処理が終わるまで、画面がフリーズしてしまいます。それを防ぐために、イベントの処理をスレッドで行います。
(本サイト入口にある)マルチスレッド版CPUエミュレータでは、知らないと自力では作れそうにないスマートな構造が使われています。(mainスレッドを引数にしたり、実行中のスレッドの属するクラスのメソッドを読んだりしています)
そこで、マルチスレッド版CPUエミュレータの骨格を抽出した簡単なプログラムを作りました。
簡単なプログラムの構造:
スレッドは次の3つです(1)MAINスレッド、(2)INスレッド、(3)OUTスレッド。
MAINは入力要請をINスレッドに出す。INスレッドはキーボードから文字列を入力してもらい、1行入力されるとMAINに知らせる。次にMAINは入力された文字列をOUTに出力してもらう(byeと入力すると終了)。
スレッド間の同期はQueue (Q) を用いて実現する:
(このプログラムのアイデアはQを各スレッド付属させ、Qへの要請を各スレッドのメソッド(put)に頼んでいる点です)
各スレッドは自分のQを見にゆき、Qに要請がなければWAITになる。WAITになっているスレッドは要請が来たときNOTIFYでWAITから解放される。
本プログラム(3スレッド)の構成をもう少し細かく追ってみる:
Step0:MAINに空の要請を送って起動する
Step1:MAINがINに入力要請を出す。
要請はINのQに送られるが、MAINはINのQに直接送るのではなく、INのputメソッドに頼んで送ってもらう。INはQをウオッチしていて入力要請があれば入力作業を行い、(INは入力文字列をMAINのmGrに書き込む)、終了をMAINのQに伝える(MAINのputを経由)。
。
MAINはINに要請を出した後、自分のQを見にいくと空なのでWAITになる。
Step2:INから入力終了情報がMAINのQに届くとWAIT状態のMAINが(NOTIFYで)目を覚ます。
Step3:MAINはOUTに出力依頼をする(OUTのQに依頼を送る)。OUTは最初自分のQを見にいって空なのでWAITになっているが、MAINからの要請で目を覚まし出力を行う。
出力終了後はMAINのQにOUTという終了信号を送る。
Step4:step1に戻る。
“bye”が入力されると、MAINは自分のQにEXITを送り、MAINではQにEXITが来ていると終了する。
MAIN watch OUT watch IN watch put Q Q put Q 入力要請 出力依頼 出力完了 入力完了 put
===
import java.io.BufferedReader;
import java.util.List;
import java.util.ArrayList;
import java.io.InputStreamReader;
////メインクラス////
public class Cmain extends Thread {
//InとOutのデータ受け渡し場所
private String mGr= null;
//オブジェクト実行待ち行列
private Q mQ = new Q();
private In mInThread= null;
private Out mOutThread= null;
// メインメソッド
public static void main( String[] pArgs ) {
Cmain cMain = new Cmain();
cMain.start();//mainスレッド実行
cMain.put( new String() );
}
private Cmain() {
// IN、OUTの各処理スレッドを開始
mInThread = new In( this );
mOutThread = new Out( this );
}
//mainスレッド
public void run () {
String aStrValue = null;
int inData=0;
try{
// 割り込まれるまでループ
while (true) {
// キューからイベントを取得
aStrValue = (String) mQ.get();
if ( aStrValue.equals( "EXIT" ) ) {
System.exit(0);
}
if(!aStrValue.equals( "IN" ))
// IN
mInThread.Input();
else{
if(mGr.equals("bye")) mQ.put( "EXIT" );
// OUT
mOutThread.put( mGr);
}
}//whileEnd
}catch(Throwable t){}
}//runEnd
//mQueueに処理結果を送る
public void put( String pStr ) {
mQ.put( pStr );
}
//mGrに文字列を書込む。
void setGr(String pStr ) {
mGr= pStr;
}
}
////IN入力スレッド////
class In extends Thread {
//メインクラス
private Cmain mMain = null;
//オブジェクト実行待ち行列
private Q iQ = new Q();
//コンストラクタ
public In( Cmain pMain ) {
mMain = pMain;
iQ = new Q();
this.start();
}
public void run() {
String aInput = null;
BufferedReader aBufferedReader = null;
while ( true ) {
try {
// キューからイベントを取得
aBufferedReader = (BufferedReader) iQ.get();
aInput = aBufferedReader.readLine();
mMain.setGr( aInput );
mMain.put( "IN" );
} catch ( Exception e ) {}
}
}
//データ入力Input処理
void Input() {
// 標準入力
BufferedReader input
= new BufferedReader(new InputStreamReader(System.in));
iQ.put( input );
}
}
////OUT処理クラス////
class Out extends Thread {
//メインクラス
private Cmain mMain = null;
//オブジェクト実行待ち行列
private Q oQ = new Q();
// コンストラクタ(mainが引数)
public Out( Cmain pMain ) {
mMain = pMain;
oQ = new Q();
this.start();
}
public void run() {
String aOutput = null;
while ( true) {
try {
// キューからイベントを取得
aOutput = (String) oQ.get();
System.out.println( new String(aOutput.toString()) );
mMain.put( "OUT" );
}
catch ( Throwable t ) { break;}
}
}
//OUTPUTする文字列を格納する。
void put( String pString ) {
oQ.put( pString );
}
}
////待ち行列のクラス////
class Q {
// 待ち行列リスト
private List mQueue = null;
//初期設定を行う
public Q() {
mQueue = new ArrayList();
}
//pObj オブジェクトをQueueに入れる
public synchronized void put( Object pObj ) {
// オブジェクトを追加する
mQueue.add( pObj );
// waitで待っているスレッドに通知(走らせる)
if( mQueue.size() == 1 ) {
notify();
}
}
// Queueからオブジェクトを取り出す。
// オブジェクトがない場合、スレッドはwaitで待つ
public synchronized Object get()
throws InterruptedException {
Object aObjRet = null;
while ( true ) {
// 待ち行列バッファにオブジェクトが溜まるまで待つ
if ( mQueue.isEmpty() ) {
wait();
}
if ( mQueue.isEmpty() ) {
// 解放後にもなければ待ち状態に移行
continue;
}
aObjRet = mQueue.get(0);
mQueue.remove(0);
break;
}
return aObjRet;
}
}
===