Javaプログラミング練習問題(スレッド)

Java

こんにちは。ECF Tech
ブログ担当 Michiharu.Tです。

ここから発展編ということで、様々なテーマを練習問題形式で
取り上げてみたいと思っています。

引き続き、どうぞよろしくお願いいたします。
今回のテーマはスレッドです。

一般的なJavaの学習順序に沿ったテーマの一覧(※随時追加)は、こちらのリンクよりご確認頂けます。

Javaプログラミング

Javaのトピックスです。主に初心者向けにプログラミング学習ネタを提供していきます。

問題編

Q001

マルチスレッドで動作するプログラムを作成します。提供コードのコメントにしたがって適切なプログラムを記述し、実行結果と同じ結果を得てください。

提供コード

(MyThread.java)

//THreadクラスを継承してください。
public class MyThread extends Thread{
    //適切なメソッド定義をしてください。
    /*ここにメソッドを定義*/ {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName() + ":" + i);
        }
    }
}

(ThreadTest01.java)

public class ThreadTest01 {
    public static void main(String[] args) throws Exception {
        Thread t1 = new MyThread();
        Thread t2 = new MyThread();

        //t1のスレッドをスタートしてください。
        t1.start();
        //t2のスレッドをスタートしてください。
        t2.start();
    }
}

実行結果

Thread-0:0
Thread-0:1
Thread-1:0
Thread-1:1
:
:
Thread-0:99
Thread-1:99

確認のポイントは下記になります。

  • Thread-0Thread-1が混在して表示されること

Q002

次のプログラムを実行すると、t1のスレッドが終了しました。の表示が実際にt1のスレッドが終了する前に表示されてしまいます。提供コード(ThreadTest02.java)中のコメントにしたがって処理を追加し、実行結果のとおりに表示されるようにしてください。

提供コード

(MyThread.java)

public class MyThread extends Thread{
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(this.getName() + ":" + i);
        }
    }
}

(ThreadTest02.java)

public class ThreadTest02 {
    public static void main(String[] args) throws Exception {
        Thread t1 = new MyThread();
        t1.start();
        //ここに処理を追加

        System.out.println("t1のスレッドが終了しました。");
    }
}

実行結果

Thread-0:0
Thread-0:0
Thread-0:1
Thread-0:2
:
:
Thread-0:48
Thread-0:49
t1のスレッドが終了しました。

※最後の表示がt1のスレッドが終了しました。になります。

Q003

下の仕様に基づいて、必要なプログラムを追記してください。

仕様

提供コードの一連のプログラムは、下図のように1つのデータ(Resource)を共有する2つのスレッドの動作を確認するものです。

MyThreadクラスのインスタンスである2つのスレッドは、Resourceインスタンスのupdateメソッドを100回呼び出す処理となっています。updateメソッドはフィールドsumを1加算する処理なので、2つのスレッドが100回ずつ呼び出した後はsunの値が200になるようにしたいと考えています。

ですが現状は、2つのスレッドが同時にResourceインスタンスを利用してしまうことがあり、必ずしも200になりません。

提供コードに適切な記述を追加し、実行結果のとおりとなるようにしてください。

提供コード

(Resource.java)

public class Resource{
    private int sum = 0;
    public void update() throws Exception{
        int x = sum;
        Thread.sleep(2);
        sum = x + 1;
    }
    public int getSum(){
        return sum;
    }
}

(MyThread.java)

public class MyThread extends Thread{
    private Resource res;

    public MyThread(Resource res){
        this.res = res;
    }

    public void run(){
        try{
            for(int i = 0; i < 100; i++){
                res.update();
                System.out.println( getName() + ":" + res.getSum());
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

(ThreadTest03.java)

public class ThreadTest03 {
    public static void main(String[] args) throws Exception {
        Resource res = new Resource();

        Thread t1 = new MyThread(res);
        Thread t2 = new MyThread(res);

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(res.getSum());
    }
}

実行結果

Thread-0:1
Thread-1:2
Thread-0:3
:
Thread-1:199
Thread-0:200
200

※最後の表示が200になります。

Q004

下の仕様に基づいて、必要なプログラムを追記してください。

仕様

提供コードの一連のプログラムは、Sender(送信者)インスタンスとReceiver(受信者)インスタンスが共有の領域(Resourceインスタンス)を使用して値の送受信をするプログラムです。

このプログラムでは下図2パターンのように、スレッドの待機・再開をコントロールする必要があります。

  • パターン1(Senderが待機するケース)

  • パターン2(Receiverが待機するケース)

ですが現状は、図の赤色部分が未実装なため、実行結果のように正しく表示されません。

次の処理手順となるようにプログラムを追加し、実行結果と同じになるようにしてください。

(処理手順)

  • Resourceクラス
    • setDataメソッド
      • dataフィールドが0でない場合は、実行中のスレッドを待たせる。
      • 引数dataの値をdataフィールドに代入する。
      • このインスタンスで待機状態になっているスレッドをすべて再開させる。
    • getDataメソッド
      • dataフィールドが0の場合は、実行中のスレッドを待たせる。
      • dataフィールドの値を戻り値として退避。
      • dataフィールドに0を代入

提供コード

(Resource.java)

public class Resource{
    private int data = 0;

    public synchronized void setData(int data){
        this.data = data;
    }

    public synchronized int getData(){
        int ret = this.data;
        this.data = 0;
        return ret;
    }
}

(Sender.java)

public class Sender extends Thread{
    private Resource res;

    public Sender(Resource res){
        this.res = res;
    }

    public void run() {
        for(int i = 1; i < 100; i++){
            res.setData(i);
        }
    }
}

(Receiver.java)

public class Receiver extends Thread{
    private Resource res;

    public Receiver(Resource res){
        this.res = res;
    }

    public void run() {
        for(int i = 1; i < 100; i++){
            System.out.println(res.getData());
        }
    }
}

実行結果

1
2
3
:
98
99
  • 値が順番に表示されます。

解答編

Q001

処理追加が必要なクラスのみを示しています。
(MyThread.java)

//THreadクラスを継承してください。
public class MyThread extends Thread{
    //適切なメソッド定義をしてください。
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName() + ":" + i);
        }
    }
}

解説

  • Javaでスレッドを作成する基本的な方法は大きく下記の2つです。
    1. Threadクラスを継承する。
    2. Runnableインタフェースを実装する。
  • 今回はThreadTest01.java側で、MyThreadクラスのインスタンスをそのままスレッドとして利用しているため、1. の方法をとります。
  • スレッドとして実行される部分はrunメソッドをオーバーライドして作成します。
  • 但し、スレッドとして実行する際はstartメソッドを呼び出します。

Q002

処理追加が必要なクラスのみを示しています。
(ThreadTest02.java)

public class ThreadTest02 {
    public static void main(String[] args) throws Exception {
        Thread t1 = new MyThread();
        t1.start();
        //ここに処理を追加
        t1.join();
        System.out.println("t1のスレッドが終了しました。");
    }
}

解説

問題のプログラムはメインスレッド(mainメソッドから始まる処理)が、t1スレッドの終了を待たずに即座にメッセージを表示しているため発生してます(下図)。

joinメソッドを使うことで、該当のスレッドの完了を待ってから自身のスレッドを再開するように動作できます(下図)。

Q003

処理追加が必要なクラスのみを示しています。
(Resource.java)

public class Resource{
    private int sum = 0;
    public synchronized void update() throws Exception{
        int x = sum;
        Thread.sleep(2);
        sum = x + 1;
    }

    public int getSum(){
        return sum;
    }
}

解説

updateメソッドにsynchronizedキーワードを追加します。追加することで、このメソッドが複数のスレッドで同時に実行されないようにすることができます。複数のスレッドから同時に実行されようとすると一方のメソッドは待たされることになり、先に実行したスレッドが、同メソッドを終了した後に実行できます。

Q004

処理追加が必要なクラスのみを示しています。
(Resource.java)

public class Resource{
    private int data = 0;

    public synchronized void setData(int data){
        if(this.data != 0){
            //実行中スレッドを待機
            try{
                this.wait();
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        //値の代入
        this.data = data;
        try{
            //スレッドの再開を通知
            this.notifyAll();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public synchronized int getData(){
        if(this.data == 0){
            //実行中スレッドを待機
            try{
                this.wait();
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        //値の取得
        int ret = this.data;
        this.data = 0;
        try{
            //スレッドの再開を通知
            this.notifyAll();
        } catch (Exception e){
            e.printStackTrace();
        }
        return ret;
    }
}

解説

Senderが使用するsetDataメソッドでは、

  • dataの値が0でない場合は待機(waitメソッド)
  • dataに値を設定した後は他スレッドに再開通知(notifyAllメソッド)

を行っています。

Receiverが使用するgetDataメソッドでは、

  • dataの値が0の場合は待機(waitメソッド)
  • dataの値を取得した後は他スレッドに再開通知(notifyAllメソッド)
    を行っています。

問題文中の図に各メソッドの実装箇所を表示すると下のようになります。

  • パターン1(Senderが待機するケース)

  • パターン2(Receiverが待機するケース)

おわりに

本日は以上となります。最後までご覧くださりありがとうございます。スレッドは取り扱いにくく、活用には十分な知識と丁寧なプログラミングが求められます。今回は基本的な知識だけですが、少しでもお役に立てましたら幸いです。ひきつづき、問題の提供をしていければと思っています。今後もよろしくお願いいたします。


合同会社イー・シー・エフでは、子ども向けプログラミングなどの教育講座を実施しています。プログラミング教室の案内や教育教材の情報、また関連するご相談・問い合わせにつきましては下記よりご確認ください。

ECFエデュケーション
タイトルとURLをコピーしました