Java synchronized修飾子/ブロック - マルチスレッド環境での排他制御

synchronized

マルチスレッド環境で共有データを操作する場合、処理の衝突に注意する必要があります。synchronized修飾子は、マルチスレッド環境で排他制御を行うときに利用されます。

synchronizedを使用しない場合

次は、インスタンス変数countを複数のスレッドで同時に増加させる処理です。

package com.devkuma.basic.thread;

public class SynchronizedSample {
    private int count;
    private static int THREAD_MAX = 300000;

    private static class MyThread implements Runnable {
        private SynchronizedSample _count;

        public MyThread(SynchronizedSample count) {
            this._count = count;
        }

        @Override
        public void run() {
            _count.increment();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedSample ss = new SynchronizedSample();
        Thread[] ts = new Thread[THREAD_MAX];
        for (int i = 0; i < THREAD_MAX; i++) {
            ts[i] = new Thread(new MyThread(ss));
            ts[i].start();
        }
        for (int i = 0; i < THREAD_MAX; i++) {
            ts[i].join();
        }
        System.out.println(ss.count);   // 結果: 299944(毎回結果が異なります)
    }

    public void increment() {
        this.count++;
    }
}

上のコードは正しく動作しません。上記のthis.count++演算では、countの値を読み取った後、増加して再代入される前に別のスレッドが割り込む可能性があるためです。
そのため、本来の最終結果は300,000になるべきですが、299999のような値が返される場合があります。

synchronizedメソッド

これを防ぐには、incrementメソッドにsynchronized修飾子を付けます。

public synchronized void increment() {
    this.count++;
}

こうすることで、incrementメソッドが複数のthreadから同時に呼び出されることはなくなります。後から呼び出された側は、先に入った処理が終わるまで待機します。そのため、結果は毎回300,000になります。

synchronizedブロック

上のコードは、synchronizedブロックを使用して次のように書き直すこともできます。

public void increment() {
    synchronized(this) {
        this.count++;
    }
}