まめぞうの技術メモ

IT関連で勉強したことをメモします

Javaのスレッド間で変数を共有する際の注意点(Synchronizedで制御)

さて、これまで沢山のJavaのスレッドについての記事を書いてきました。

今日は、スレッド間での変数を共有する際の注意点について紹介します。

スレッド間で変数を共有する際の注意点

複数のスレッド間で一つの変数を共同で使いたい場面ってあると思います。

その際に、複数のスレッドで一つの変数を操作した時、意図せず値が戻ってしまったり、おかしな値になってしまったりしないように、注意する必要があります。

言葉で書いても分かり難いので、実際の例を見てみましょう。

例:複数のスレッドで一つの変数をカウントアップ

これはダメな例です。複数のスレッドで一つのクラスのカウンタを加算していきます。

ThreadRun6Counter.java

まずはカウンタから。static変数 counter を、同じくstaticのメソッド「showAndCountup()」を使って加算、加算後の値を返却します。

つまり、counterが0のときに、「showAndCountup()」を実行すれば、counterが1プラスされて、1が返却されるという単純なものです。

/**
 * カウンターの値を保持するクラス
 * showAndCountUpメソッドで、カウンターを追加して、
 * 追加したカウンターの値を返却する。
 */
public class ThreadRun6Counter {
    public static int counter = 0;

    public static int showAndCountUp(){
        counter = counter + 1;
        return  counter;
    }
}

Thread6Run.java

次は、スレッドです。メインメソッドから呼び出されます。

先程のThread6Counterの「showAndCountup()」をひたすら実行します。

実行したら、カウンタの値を出力します。

/**
 * 呼び出されるスレッド。
 * ThreadRun6Counter.showAndCountUp()を繰り返し実行する。
 */
public class ThreadRun6 extends Thread{
    public void run(){

        // iが100になるまでカウントを繰り返す
        for(int i=0; i<100; i++){

            //ThreadRun6Counterのカウントを加算して、結果を出力。
            int cnt = ThreadRun6Counter.showAndCountUp();
            System.out.println(
                    "Thread: " + i + "回目の実行。 カウンターは、  " + cnt + "です。");

            // スリープ1秒待つ
            try {
                sleep(1000);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }

    }
}

ThreadCall6.java

最後はメインとなるThreadCall6クラスです。

ThreadRun6からスレッドを一つ生成します。

また、mainメソッド内で、自分もThread6Counterの「showAndCountup()」をひたすら実行します。

/**
 * メインメソッドを持つクラス。
 * Thread6Runクラスからスレッドを起動。
 * 自身もメインメソッド内で、
 * ThreadRun6Counter.showAndCountUp()を繰り返し実行する。
 */
public class ThreadCall6 {

    public static void main(String[] args){

        // ThreadRunクラスをインスタンス化
        ThreadRun6 t = new ThreadRun6();

        // ThreadRunクラスのインスタンス t をstart()して、スレッド開始
        t.start();

        // iが100になるまでカウントを繰り返す
        for(int i=0; i<100; i++){

            //ThreadRun6Counterのカウントを加算して、結果を出力。
            int cnt = ThreadRun6Counter.showAndCountUp();
            System.out.println("Main:   " + i + "回目の実行。 カウンターは、  " + cnt + "です。");

            try{
                // スリープ1秒待つ
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

動かしてみる

さて、実行するとどうなるでしょうか?

メイン側もスレッド側も、一つずつThreadRun6Counterをカウントアップできていれば成功ですね。

変数カウントアップ結果(失敗)

順番は滅茶苦茶だし、同じ値が何度も出力されてるし、全くカウントアップ出来てないですね。

Synchronizedを使って複数スレッドの操作の排他制御をすれば解決

さて、正しくカウントアップするにはどうすればよいか。

複数スレッドでバラバラとカウントアップするのを制御して、順番にカウントアップさせればいいんですね。

つまり「排他制御」です。

やり方はとてもカンタンで、排他制御したいメソッドに「Synchronized」をつけるだけです。

ThreadRun6COunter.java

「showAndCountUp()」の頭に「Synchronized」をつけて排他制御します。

/**
 * カウンターの値を保持するクラス
 * showAndCountUpメソッドで、カウンターを追加して、
 * 追加したカウンターの値を返却する。
 */
public class ThreadRun6Counter {
    public static int counter = 0;

    synchronized public static int showAndCountUp(){
        counter = counter + 1;
        return  counter;
    }
}

実行してみると、順番にカウンタが並んでカウントアップされています。

カンタンですね!

変数カウントアップ結果(成功)

自分の勉強がてらまとめてみました。

参考になれば。