問題3-7
スレッドによる走行を表現するRacingCarクラスを作成してください。
RacingCarクラスを使用するThreadBasicPracticeクラスのmainメソッドは完成しています。
(変更は禁止です)
【RacingCarクラス】
Threadクラスを継承します。
ゴール地点までの距離を表すint型の定数GOALを定義します。
定数GOALの値は100を代入して初期化してください。RacingCarクラスは次の3つのインスタンスフィールドを持ちます。
車の名前を表すname(String型)
その車の最高走行距離を表すmaxDistance(int型)
その車のエンストする割合を表すengineStop(int型)次の引数を持つコンストラクタを1つ作成してください。
第1引数 車の名前を表すString型の引数
第2引数 最高走行距離を表すint型の引数
第3引数 エンストする割合を表すint型の引数runメソッドをオーバーライドします。2つのローカル変数を宣言します。
総走行距離を表すint型 totalMileage
一回の走行距離を表すint型 mileageランダムの雰囲気を出すために、java.utilパッケージのRandomクラスを使用します。
【Randomクラスの使い方の例】
Random r = new Random();
System.out.println(r.nextInt(6) + 1);
これで1〜6を表示するさいころの出来上がりです。runメソッドでは、ゴールするまで走行します。
まず、エンストするかどうかをRandomクラスを使って表現します。
engineStopの値が10なら、10回に1回の割合でエンストします。
エンストした場合は「○○がエンストしました!」と表示獅子、3秒間停止します。
エンストしなかった場合は走行します。
走行距離は、1から最高走行距離maxDistanceの間のランダムな値です。
そして「○○が△△km走行しました!」と表示します。
総走行距離がGOAL定数を超えていればループを抜けて「○○がゴールしました!!!!!」と表示して終了します。
まだ総走行距離がGOAL定数に達していなければ1秒停止して走行を続けます。
【実行結果の一例】
Careful号が3km走行しました! Normal号がエンストしました! Normal号が3km走行しました! ・・・ Careful号が2km走行しました! Gambler号が15km走行しました! Gambler号がゴールしました!!!!! Careful号が4km走行しました! Normal号が8km走行しました! Careful号が1km走行しました! Careful号が4km走行しました! Careful号がゴールしました!!!!! Normal号が8km走行しました! Normal号がゴールしました!!!!! レースが終了しました
【ThreadBasicPractice.java】
public class ThreadBasicPractice { public static void main(String[] args) { // 平均的な性能のnormal号 RacingCar normal = new RacingCar("Normal号", 10, 10); // スピードはあるがエンストが多いgambler号 RacingCar gambler = new RacingCar("Gambler号", 20, 3); // 安全面を重視したcareful号 RacingCar careful = new RacingCar("Careful号", 5, 1000); // 各車一斉にスタート!! normal.start(); gambler.start(); careful.start(); // 前車がゴールするのを待つ try { normal.join(); gambler.join(); careful.join(); } catch(InterruptedException e) { e.printStackTrace(); } System.out.println("レースが終了しました"); } } // ここにRacingCarクラスを作成してください
解答例
【ThreadBasicPractice.java】
import java.util.*; public class ThreadBasicPractice { public static void main(String[] args) { // 平均的な性能のnormal号 RacingCar normal = new RacingCar("Normal号", 10, 10); // スピードはあるがエンストが多いgambler号 RacingCar gambler = new RacingCar("Gambler号", 20, 3); // 安全面を重視したcareful号 RacingCar careful = new RacingCar("Careful号", 5, 1000); // 各車一斉にスタート!! normal.start(); gambler.start(); careful.start(); // 前車がゴールするのを待つ try { normal.join(); gambler.join(); careful.join(); } catch(InterruptedException e) { e.printStackTrace(); } System.out.println("レースが終了しました"); } } // RacingCarクラス class RacingCar extends Thread { // ゴール地点までの距離 public static final int GOAL = 100; // 車の名前 private String name; // 最高走行距離 private int maxDistance; // エンストする場合 private int engineStop; // コンストラクタ public RacingCar(final String name, final int maxDistance, final int engineStop) { this.name = name; this.maxDistance = maxDistance; this.engineStop = engineStop; } @Override public void run() { // 総走行距離 int totalMileage = 0; // 一回の走行距離 int mileage = 0; Random r = new Random(); while(true) { try { // エンストするかどうか if(r.nextInt(engineStop) == 0) { System.out.println(name + "がエンストしました!"); Thread.sleep(3000); } else { // 走行距離を取得 mileage = r.nextInt(maxDistance) + 1; System.out.println(name + "が" + mileage + "km走行しました!"); // 総走行距離に加算 totalMileage += mileage; // ゴールしていたらループを抜ける if(totalMileage >= GOAL) { break; } Thread.sleep(1000); } } catch(InterruptedException e) { e.printStackTrace(); } } System.out.println(name + "がゴールしました!!!!!"); } }
解説
Javaの機能のひとつに「マルチスレッド」があります。
普通、Javaプログラムを実行すると、mainメソッドから1行ずつ順番に実行されます。
このmainメソッドから始まる1本の流れを「メインスレッド」と言います。
「スレッド」とは本来「糸」という意味で、普通の実行であればmainメソッドから1本の糸がずっと順に流れていくのですが、「マルチスレッド」にすると、この糸を複数にできるのです。
こうすることで、最近主流のマルチCPUのパソコンの性能を活用しやすくなります。
ただ、マルチスレッドプログラミングは非常に難しく、普通はフレームワークなどを利用してあまり意識することなくマルチスレッドの恩恵にあずかっています。
今回は、とても基本的なスレッドの問題ですが、マルチスレッドを意識してコーディングすることも非常に重要です。
ベーシックにマルチスレッドプログラミングを行うときは、一般的にRunnableインタフェースとThreadクラスを使用します。
Runnableインタフェースは抽象メソッドであるrunメソッドを1つだけ持ち、この中に別スレッドで実行したい処理を記述します。
Runnableインタフェースを実装したクラスを定義し、runメソッドを実装します。
そのクラスをインスタンス化し、Threadクラスのコンストラクタに渡します。
あとは、Threadオブジェクトのstartインスタンスメソッドを呼び出せば、そこからスレッドの糸が枝分かれするわけです。
また、ThreadクラスもRunnableインタフェースを実装しているので、Threadクラスを継承し、runメソッドをオーバーライドすることでマルチスレッドを行うことも可能です。
(今回の問題ではこちらを採用しています)
今回はThreadクラスを継承して、レーシングカークラスを定義しました。
mainメソッドで3つのインスタンスを生成し、startメソッドを呼び出してレースを開始しています。
// 各社一斉にスタート!! normal.start(); gambler.start(); careful.start();
これで、メインスレッド+3スレッドの計4スレッドが同時に実行されるわけです。
今回、メインスレッドは3台のレーシングカーのレースを見守る存在なので、3台のレーシングカーがゴールするのを持たないといけません。
そこで、Threadクラスが持つjoinインスタンスメソッドを実行しています。
// 全車ゴールするのを待つ try { normal.join(); gambler.join(); careful.join(); } catch(InterruptedException e) { e.printStackTrace(); }
メインスレッドがレーシングカーオブジェクトのjoinメソッドを呼び出すと、そのレーシングカースレッドが終了するまで待機します。
メインスレッドは処理がピタッと止まってしまうわけです。
ただ、joinメソッドは検査例外InterruptedExceptionをスローする可能性があるので、try-catchで例外処理を記述しています。
あと、大切なものとしては、Threadクラスが持つクラスメソッドsleepがあります。
これは、引数で渡されたミリ秒間、処理をストップすることができます。
しかし、sleepメソッドも先ほどのjoinメソッドと同様、検査例外InterruptedExceptionをスローする可能性があるので例外処理が必須です。
ちなみに、今回の問題で出てきたRandomクラスによる乱数の制御とThread.sleepメソッドによる処理の一時停止、あとはSystem.console().readLine()によるキーボードからの入力受付の3つをうまく使えば、シンプルなゲームであれば何でも作れます。
自分で作ってみたいゲームを考えて、自力で試行錯誤しながらプログラミングするのはとても良い経験になるはずです。
参考図書
LINE公式アカウント
仕事が辛くてたまらない人生が、仕事が楽しくてたまらない人生に変わります。
【登録いただいた人全員に、無料キャリア相談プレゼント中!】