// pressureを Threadのサブクラスとして定義 // Threadによって動く実体とする class pressure extends Thread{ // 圧力が安全圏内なら加える static void RaisePressure(){ // 現在の圧力が制限値よりも15を超して少ないなら int pval=p.pressureGauge; if(pval < p.safetyLimit-15){ // わざと 100ms(0.1秒)待つ try{sleep(100);} catch(Exception e){} // 圧力を15加える p.pressureGauge+=15; System.out.println("pval="+pval+", p.pressureGauge="+p.pressureGauge); } else{ System.out.println("Failed adding pressure"); } } // pressureのインスタンスに対して startするとここが呼ばれる. public void run(){ RaisePressure(); } } public class p { // pクラスの静的変数なのでプログラム全体から共有される // 圧力値 static int pressureGauge=0; // 圧力の制限値 static final int safetyLimit=20; public static void main(String[] args){ pressure [] p1=new pressure[10]; int i; // 10個の Threadを作り,次々と作成 for(i=0;i<10;i++){ p1[i]=new pressure(); p1[i].start(); } // すべてのスレッドが終了するのを待つ try{ for(i=0;i<10;i++) p1[i].join(); } catch (Exception e){} System.out.println("gauge reads "+pressureGauge+", safe limit is 20"); } }これを実行すると,
pval=0, p.pressureGauge=15 pval=0, p.pressureGauge=30 pval=0, p.pressureGauge=45 pval=0, p.pressureGauge=60 pval=0, p.pressureGauge=75 pval=0, p.pressureGauge=90 pval=0, p.pressureGauge=105 pval=0, p.pressureGauge=120 pval=0, p.pressureGauge=135 pval=0, p.pressureGauge=150 gauge reads 150, safe limit is 20となり,範囲を超えてしまうだろう.これを防ぐために,Java言語では相互排 他(mutual exclusion)を実現するためのsynchronized というキーワードを用 意してある.この使い方は,メソッドにつける.ブロックにつけるなどいろい ろある.先ほどの例では,
// pressure1を Threadのサブクラスとして定義 // Threadによって動く実体とする class pressure1 extends Thread{ // 圧力が安全圏内なら加える static synchronized void RaisePressure(){ // 現在の圧力が制限値よりも15を超して少ないなら int pval=p1.pressureGauge; if(pval < p1.safetyLimit-15){ // わざと 100ms(0.1秒)待つ try{sleep(100);} catch(Exception e){} // 圧力を15加える p1.pressureGauge+=15; System.out.println("pval="+pval+", p1.pressureGauge="+p1.pressureGauge); } else{ System.out.println("Failed adding pressure"); } } // pressureのインスタンスに対して startするとここが呼ばれる. public void run(){ RaisePressure(); } } public class p1 { // p1クラスの静的変数なのでプログラム全体から共有される // 圧力値 static int pressureGauge=0; // 圧力の制限値 static final int safetyLimit=20; public static void main(String[] args){ pressure1 [] p1=new pressure1[10]; int i; // 10個の Threadを作り,次々と作成 for(i=0;i<10;i++){ p1[i]=new pressure1(); p1[i].start(); } // すべてのスレッドが終了するのを待つ try{ for(i=0;i<10;i++) p1[i].join(); } catch (Exception e){} System.out.println("gauge reads "+pressureGauge+", safe limit is 20"); } }のようにRaisePressureにsynchronized というキーワードを付けると,同じク ラスのオブジェクトが,RaisePressureを同時に実行するのを防ぐことができ る.
メソッドRaisePressureが static になっていないと,synchronizedにより, 「同時には実行しない」単位が,「同じインスタンスに対するメソッド RaisePressureの呼び出し」というものになるので,このケースではふさわしくない.
// < applet code="Test15" width=400 height=400 ></applet> import java.applet.*; import java.awt.*; public class Test15 extends Applet implements Runnable{ // スレッドの宣言 public Thread th=null; int x=50,y=20,dx=4,dy=0; public void paint(Graphics g){ g.setColor(Color.white); g.fillRect(0,0,200,200); g.setColor(Color.black); g.drawLine(0,150,200,150); g.setColor(Color.red); g.drawString("Click me", x, y); } // Runnable な Applet はまず, start メソッドが呼ばれる public void start(){ // スレッドができていない時はここで作成する if(th==null){ th=new Thread(this); th.start();} } // stop メソッドを作っておかないと, WWWブラウザで別のページに行っても動き続けてしまうことがある. public void stop(){ if(th!=null){ th=null;} } // Runnable な Appletでは, run メソッドが実行の主体となる public void run(){ while(th != null && th.isAlive()){ dy=dy+2;x=x+dx;y=y+dy; if(x<10){ x=10+(10-x); dx= -dx; } else if (x>150){ x=150-(x-150); dx= -dx; } if(y>150){ y=150-(y-150); dy= -dy;} // 画面の更新. これを忘れると変更の結果が表示されない repaint(); // Threadクラス のsleep メソッドで ミリ秒単位の sleep(休止) を指定できる. try { Thread.sleep(200); } catch(InterruptedException e){} } } }アプレットの中でアニメーション(リアルタイムゲームを含む)をするのは結構 面倒である.まず,アプレットにはmainがない(書いても呼ばれない)ので,明 示的にスレッドを作る必要がある.アプレットは Applet クラスのサブクラス で作らなくてはいけないので,Runnable インタフェースを継承することになる.
mainがないのでどこで,実行のためのスレッドを作るかということだが, initメソッドの中で作るのも可能だが,一度スレッドを作るとWWWブラウザで 他のページに移っても動き続けるというのはやっかいなので,アプレットのあ るページに移動した際に呼ばれる start メソッドの中で作って,アプレット のあるページから抜けた際に呼ばれる stop メソッドの中で終了させるのが一 般的である.BallGame.java をアプレットとして書き直したのが以下 のプログラムである.
// <applet code=BallGameApplet width=300 height=600></applet> import java.applet.*; import java.awt.*; import java.awt.event.*; public class BallGameApplet extends Applet implements KeyListener, MouseListener, Runnable{ // ボールの座標,速度のx, y成分の宣言 int ball_x=100,ball_y=100,ball_vx=16,ball_vy=12; // ボールの大きさ int ball_size=20; // 画面の幅,高さ int width=300, height=600; // バーの座標 int bar_x=0, bar_y=550; // バーの速度 int bar_vx=0; // バーの幅,高さ int bar_width=60,bar_height=10; // BallGameクラスのコンストラクタ // アニメーションを行うためのスレッド public Thread th=null; // WWWブラウザがアプレットを含むページに来たときに呼ばれる. public void init(){ System.out.println("init is called"); addKeyListener(this); addMouseListener(this); requestFocus(); } public void start(){ System.out.println("start is called"); // スレッドができていない時はここで作成する if(th==null){ th=new Thread(this); th.start();} } public void stop(){ System.out.println("stop is called"); // スレッドがある時はスレッドを止める th=null; } // 上のstartメソッドの中で,th.start()が呼ばれるとスレッドからこの // メソッドが呼ばれる. public void run(){ Thread thisThread=Thread.currentThread(); while(th==thisThread){ // 0.1秒(100ミリ秒)スリープする try { th.sleep(100); } catch(Exception e){} // ボール,バーの移動をおこなう timeTick(); } } void timeTick(){ // バーの移動 bar_x=bar_x+bar_vx; // 左端から飛び出そうになったら左端に合わせる if(bar_x<0) bar_x=0; // 右端から飛び出そうになったら右端に合わせる else if(bar_x+bar_width >width) bar_x=width-bar_width; // ボールの移動.古い座標を保存 int old_x=ball_x; int old_y=ball_y; // 速度に従って,次の座標を決定 ball_x=ball_x+ball_vx; ball_y=ball_y+ball_vy; // 左端から飛び出そうになったら,反射させる if(ball_x<0){ ball_x= -ball_x; ball_vx= -ball_vx; } // 右端から飛び出そうになったら,反射させる else if(ball_x >width-ball_size){ ball_x=(width-ball_size)-(ball_x-(width-ball_size)); ball_vx= -ball_vx; } // 上端から飛び出そうになったら,反射させる if(ball_y<0.0){ ball_y= -ball_y; ball_vy= -ball_vy; } // バーのある線を通過しそうになったら, else if(ball_y >bar_y && old_y<=bar_y){ // バーのある線を横切るX座標を計算 int x=old_x+(ball_x-old_x)*(bar_y-old_y)/(ball_y-old_y); // バーと接触している場合は反射させる. if(bar_x <= x && x<=bar_x+bar_width){ ball_y=bar_y-(ball_y-bar_y); ball_vy= -ball_vy; } } // 下端から飛び出そうになったら,反射させる else if(ball_y > height){ ball_vy=-ball_vy; } // 再表示 repaint(); } public void update(Graphics g){ paint(g); } public void paint(Graphics g){ // 描画色を黒にする. g.setColor(Color.black); // 全体を塗り潰す g.fillRect(0,0,width,height); // 描画色を白にする. g.setColor(Color.white); // 警告文字列を(0,200)の点を左下にして描く g.drawString("Not a game, but a excercise in CP1(Tue/tanaka)",0,200); // 描画色を赤にする. g.setColor(Color.red); // ボールを描く g.fillOval(ball_x,ball_y,ball_size,ball_size); // オフスクリーンイメージへの描画色を青にする. g.setColor(Color.blue); // バーを描く g.fillRect(bar_x,bar_y,bar_width,bar_height); } // キーが押された時にこのメソッドが呼ばれる. public void keyReleased(KeyEvent e){} public void keyTyped(KeyEvent e){} public void keyPressed(KeyEvent e){ System.out.println("keyPressed("+e+")"); int key=e.getKeyChar(); // 'h' のキーが押された時は,バーの移動速度を -10に if(key=='h'){ bar_vx= -10; } // 'l' のキーが押された時は,バーの移動速度を 10に else if(key=='l'){ bar_vx=10; } // 'j' のキーが押された時は,バーの移動速度を 0に else if(key=='j'){ bar_vx=0; } else if(key=='q'){ System.exit(0); } } public void mouseReleased(MouseEvent e){} public void mousePressed(MouseEvent e){ System.out.println(e); } public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} }アプレット