synchronized 修飾子

Revised: Apr./6th/2002; Since: Mar./23rd/2002

メソッドに対して、 synchronized 修飾子を指定することができます。この修飾子で指定されたメンバーは、最大でも一つのスレッドにしか支配されません。あるスレッドがこのメンバにアクセスしているときに別のスレッドが重ねてアクセスしようとすると、先に使っていたスレッドによる処理が終了するまで待機させられます。

synchronized 修飾されたメンバーは、一つのスレッドにアクセスされるとロックされて、マルチスレッドによる同時アクセスを防げます。このようなコードを持ったオブジェクトのことをモニターと呼びます。

サンプル

同期指定しないために不整合が起こる例

次のサンプルは、5つのスレッドを発生し、共有オブジェクトのデータにアクセスしています。

それぞれのスレッドは、同じオブジェクトのデータを取得し、1を加えていますが、同期指定していないので、不整合が起こっています。

不整合を起こす為に Thread.sleep() を使っていますが、通常はそれだけの時間が掛かる処理が存在することになります。

// 共有するデータを保持するクラス
class Share {
	private int x = 0;

	public void addX() {
		int a = x;

		// 2秒間スリープ
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			System.out.println("Share: " + e);
		}

		x = a + 1;
		System.out.println(a);
	}

	public int getX() {
		return x;
	}
}

// マルチスレッドで実行するクラス
class Sync implements Runnable {
	// 共有オブジェクト
	Share var;

	// コンストラクタ
	Sync(Share obj) {
		var = obj;
	}

	public void run() {
		var.addX();
	}
}

// コントロールクラス
class SyncTest {
	public static void main(String[] args) {
		// 共有オブジェクト
		Share obj = new Share();
		System.out.println("count: " + obj.getX());

		// スレッドの配列の宣言
		Thread[] thres = new Thread[5];

		// 5つのスレッドの作成
		for (int i=0; i<5; i++) {
			// Runnable オブジェクトをスレッドに委譲
			thres[i] = new Thread(new Sync(obj));
			// スレッドの開始
			thres[i].start();
		}

		// 全てのスレッドが終了するまで待機
		for (int i=0; i<5; i++) {
			try {
				thres[i].join();
			} catch (InterruptedException e) {
				System.out.println("main(): " + e);
			}
		}

		System.out.println("count: " + obj.getX());
	}
}

このサンプルでは、五つのスレッドが、共有オブジェクトのデータを取得して、それに1づつ加算していきます。想定した動作としては、最後は総計で5が加算されているはずです。しかし、このサンプルでは加算する前に、次のスレッドが共有データを取得してしまうので上手く蓄積しません。

図: 同期指定しない不整合の例
C:\Java\Thread>javac SyncTest.java

C:\Java\Thread>java SyncTest
count: 0
0
0
0
0
0
count: 1

同期指定した例

次のものは、メソッドに同期指定をつけた例です:

// 共有するデータを保持するクラス
class Share {
	private int x = 0;

	// 同期指定したメソッド
	public synchronized void addX() {
		int a = x;
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			System.out.println("Share: " + e);
		}
		x = a + 1;
		System.out.println(a);
	}

	// 同期指定したメソッド
	public synchronized int getX() {
		return x;
	}
}

// マルチスレッドで実行するクラス
class Sync implements Runnable {
	Share var;

	Sync(Share obj) {
		var = obj;
	}

	public void run() {
		var.addX();
	}
}

// コントロールクラス
class SyncTest {
	public static void main(String[] args) {
		Share obj = new Share();
		System.out.println("count: " + obj.getX());

		Thread[] thres = new Thread[5];

		for (int i=0; i<5; i++) {
			thres[i] = new Thread(new Sync(obj));
			thres[i].start();
		}

		for (int i=0; i<5; i++) {
			try {
				thres[i].join();
			} catch (InterruptedException e) {
				System.out.println("main(): " + e);
			}
		}

		System.out.println("count: " + obj.getX());
	}
}

この例では、 addX()synchronized 修飾子をつけているので、一つのスレッドが実行している間は、他のスレッドはアクセスできず、ブロックされて実行待機状態になります。このことによって、前のサンプルで見たような不整合が解決されています。

C:\Java\Thread>javac SyncTest.java

C:\Java\Thread>java SyncTest
count: 0
0
1
2
3
4
count: 5


Copyright © 2002 SUGAI, Manabu. All Rights Reserved.