Geekroid-ギークロイド(仮)|ITエンジニアの日常をささいな情報で彩るコラム

ITエンジニアの転職

synchronized修飾子/ブロック

マルチスレッド環境における排他制御 – synchronized修飾子/ブロック

マルチスレッド環境で、共有のデータを操作する際には、処理の競合に注意です。

たとえば以下は、インスタンス変数countを複数のスレッドから同時にインクリメント操作する例ですが、このコードは正しく動作しません。というのも、「this.count++」という演算で、count値が読み出されてからインクリメントして再代入される前に、他のスレッドが割り込む可能性があるためです。そのため、本来、最終的な解は30万になるはずですが、そうはならず、たとえば29988のような値が返されます。

SynchronizedSample.java

package com.example.mynavi.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++;
  }
}

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

SynchronizedSample2.java(抜粋)

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

これによって、incrementメソッドは複数のスレッドから同時に呼び出されることがなくなります(後から呼び出した方は、先行する処理が終わるまで待機します)。結果、今度は正しく30万という解が得られます。

attention

ただし、synchronized修飾子が正しく働くのは、メソッドが属するインスタンスが一致している場合です。異なるインスタンスが存在する場合、インスタンスの数だけ同時実行する可能性がありますので、注意してください。

上のコードは、synchronizedブロックを使って、以下のように書き換えることもできます。

SynchronizedSample3.java(抜粋)

public void increment() {
  synchronized(this) {
    this.count++;
  }
}
スレッド
Threadクラス/Runnableインターフェイス
synchronized修飾子/ブロック
ReentrantLockクラス
AtomicXxxxxクラス
Executorsクラス

Geekroid-ギークロイド(仮)|ITエンジニアの日常をささいな情報で彩るコラム

ITエンジニアの転職