23. synchronized修飾子

排他制御をしたいときのキーワードです。

(1) 2つのスレッドが1つのオブジェクトを共有する例

 今回はまず、いくつかのスレッドがカウンターの値を増やしていくプログラムをつくってみます。まず、カウンタークラスとして次のような Counter クラスを作ります。これはメンバ変数に counter、このカウンターの値を増やすメソッドで countUp( ) を作ります。カウントアップしたあとは Thread.sleep( ) を使って少しだけ待ち時間をいれます。

class Counter {
    private int counter;
    public void countUp() {
        counter++;
        try {
            Thread.sleep(500);
        } catch (Exception e) { }
        System.out.println("カウンターの値 : "+counter);
    }
}


 次にカウンターの値を増やす MyThread クラスを作ります。このクラスはマルチスレッドで動くようにしたいので、Thread クラスのサブクラスにしてつくります。複数のスレッドで、上でつくったカウンターオブジェクトを共有できるようにしたいので、MyThread クラスのコンストラクタで Counter クラスのオブジェクトを受け取れるようにしておきます。

class MyThread extends Thread {
    private Counter cnt;
    public MyThread(Counter c) {
        cnt = c;
    }
    public void run() {
        cnt.countUp();
    }
}        

 最後にこの2つのクラスを動かすクラスを作ります。この例では2つのスレッドをつくりカウンターの値を更新してみます。

  MultiCounter.java (上の2つのクラスのソースも含んでいます)

class Counter {
    private int counter;
    public void countUp() {
        counter++;
        try {
            Thread.sleep(500);
        } catch (Exception e) { }
        System.out.println("カウンターの値 : "+counter);
    }
}

class MyThread extends Thread {
    private Counter cnt;
    public MyThread(Counter c) {
        cnt = c;
    }
    public void run() {
        cnt.countUp();
    }
}   

class MultiCounter {
    public static void main(String args[]) {
        Counter c = new Counter();
        MyThread kick1 = new MyThread(c);
        MyThread kick2 = new MyThread(c);
        kick1.start();
        kick2.start();
    }
}

 さて、このクラスを実行するとどうなるでしょう?スレッドを順番に実行させているので、表示されるカウンターの値は「1」「2」と出てくるでしょうか?

  実行例

C:\java_test>java MultiCounter
カウンターの値 : 2
カウンターの値 : 2


(2) synchronized による割り込み禁止

 上の例ではカウンターの値が「2」「2」と表示されてしまいました。どうしてこのようなことが起こってしまったのか考えてみます。

 いま、2つのスレッドは1つの Counter オブジェクトを共有してします。それぞれのスレッドがカウンターの値を増やすためには Counter クラスの countUp( ) メソッドを使います。

 2つのスレッドがほぼ同時に countUp( ) を実行したときのことを考えてみましょう。1つ目のスレッドが countUp( ) を実行して Thread.sleep( ) に入っている間に、2つ目のスレッドが conuter の値を増やしてしまいます。ということは、System.out.println( ) で表示される Conuter クラスのメンバ変数 counter の値は両方とも「2」になってしまうわけです。

 本当はカウンターの値を増やして表示するまでは他のスレッドに countUp( ) を実行されたくないのですが、途中で割り込まれてしまい、この例のようにカウンターの値が正しく表示されなくなってしまいました。

 このようにメソッド実行中に途中で他のスレッドに割り込まれたくない場合は synchronized(シンクロナイズド)というキーワードを使います。このキーワードがついたメソッドはこのあるスレッドが実行中の場合、他のスレッドが実行できなくなります。

 synchroinzed を使って Counter クラスを書き直すと次のようになります。

  MultiCounter.java (修正版)

class Counter {
    private int counter;
    public synchronized void countUp() {
        counter++;
        try {
            Thread.sleep(500);
        } catch (Exception e) { }
        System.out.println("カウンターの値 : "+counter);
    }
}

class MyThread extends Thread {
    private Counter cnt;
    public MyThread(Counter c) {
        cnt = c;
    }
    public void run() {
        cnt.countUp();
    }
}   

class MultiCounter {
    public static void main(String args[]) {
        Counter c = new Counter();
        MyThread kick1 = new MyThread(c);
        MyThread kick2 = new MyThread(c);
        kick1.start();
        kick2.start();
    }
}

 これだけで、途中で割り込まれることなくカウンターの正しい値を表示できるようになります。

 実行例

C:\java_test>java MultiCounter
カウンターの値 : 1
カウンターの値 : 2


(3) synchronized の詳細

 もし、同じクラスの synchronized 指定のメソッドが2つあった場合は、どちらかのメソッドしか実行できなくなります。たとえば、つぎのように countUp( ) と countDown( ) を作ると、同時に実行できるメソッドはどちらか1つになります。

class Counter {
    private int counter;

    public synchronized void countUp() {
            counter++;
            try {
                Thread.sleep(500);
            } catch (Exception e) { }
            System.out.println("カウンターの値 : "+counter);
    }

    public synchronized void countDown() {
            counter--;
            try {
                Thread.sleep(500);
            } catch (Exception e) { }
            System.out.println("カウンターの値 : "+counter);
    }
}

 このように1つのクラス内に複数の synchronized メソッドを作った場合、ある synchronized メソッドの実行が終わらないと、他の synchronized メソッドは実行することができなくなります。


synchronized キーワードはブロック単位に指定をすることもできます。

前の章(22.Runnableインタフェース)   次の章(24.Socketを使った通信)