同期


相互排他スレッド

複数のスレッドが何らかの処理を並行的に実行しているプログラムでは
特定の変数やメソッドに対して並列にアクセスしてしまう可能性があります
例えば、あるメソッドを実行している途中に、別のスレッドが割り込んでくる可能性もあるのです
このような状態が好ましくない場合、スレッド同士が何らかの方法で通信しあい
不具合が発生しないよう、互いの処理を制御するように同期させる必要があります

同期はいわゆる排他的処理であり、一方がONのとき他方はOFFという処理です
スレッドがメソッドを実行している間、他のスレッドはそのメソッドの実行を待機させる
こうした処理を同期を取ることで実現することができるのです

同期しなければいけないケースはどのような場合かというと
複数のスレッドが同一のデータを共有している状態です
例えば、銀行のデータサーバーに複数の端末から同時にアクセスし
同一の口座(データ)から現金を引き出した瞬間を想像してください

一方の端末が現金の引き出しを実行した瞬間から
プログラムが実際に、データサーバーの現金の値を変更する間に
もう一方の端末が同様の処理をした場合、データの整合性が合わなくなります

データを処理する順序が重要な処理では、並列実行に注意してください
例えば、次のプログラムを実行してみてください
class Test extends Thread {
	static int var1 , var2;

	public static void main(String args[]) {
		Test obj = new Test();
		obj.start();

		for(int i = 0 ; i < 10 ; i++) obj.add();		
	}

	void add() {
		var1++;
		System.out.println("処理1 : var1 = " + var1 + " : var2 = " + var2);

		try { Thread.sleep(10); }
		catch(InterruptedException err) { }

		var2++;
		System.out.println("処理2 : var1 = " + var1 + " : var2 = " + var2);
	}

	public void run() {
		for(int i = 0 ; i < 10 ; i++)  add();
	}
}
Test クラスの静的変数 var1 と var2 は、add() メソッドによって加算されます
この add() メソッドを main() メソッドを実行している主スレッドと
新しく生成したスレッドが実行する run() メソッドから無秩序に呼び出しています

add() メソッドでは var1 をインクリメント後 10 ミリ秒のスレッド待機を命じています
そのため、スレッドが add() メソッドの途中まで実行している段階で
別のスレッドが add() メソッドを実行するということが発生してしまいます
結果として var2 がインクリメントされていない状態で var1 がインクリメントされてしまい
var1 と var2 変数の差が 1 以上になってしまうことがあります

変数 var1 と var2 を add() メソッドでインクリメントする処理を正しい順序で実行し
複数のスレッドが関与しながらも、var1 と var2 の加算順序を逐次的に行いたい場合
同じメソッドを同時に実行しないよう同期をとります
同期を取るには synchronized 修飾子をメソッドに指定します

sinchronized が指定されたメソッドは、実行中のオブジェクトにロックを渡します
そのオブジェクトがメソッドを終了するまで、そのメソッドはロックを解放しません
別のスレッドから同じメソッドを実行しようとした場合、ロックが解放されるまで待機します
メソッドの実行を終了すると、メソッドはオブジェクトのロックを解放するため
メソッドの実行を待機していたスレッドがそのメソッドを実行できるようになります
class Test extends Thread {
	static int var1 , var2;

	public static void main(String args[]) {
		Test obj = new Test();
		obj.start();

		for(int i = 0 ; i < 10 ; i++) obj.add();		
	}

	synchronized void add() {
		var1++;
		System.out.println("処理1 : var1 = " + var1 + " : var2 = " + var2);

		try { Thread.sleep(10); }
		catch(InterruptedException err) { }

		var2++;
		System.out.println("処理2 : var1 = " + var1 + " : var2 = " + var2);
	}

	public void run() {
		for(int i = 0 ; i < 10 ; i++)  add();
	}
}
このようにJavaは同期の排他的処理を言語レベルで指定することができます
このプログラムを実行すると、add() メソッドは常に 1 つのスレッドしか実行できず
何らかのメソッドが add() メソッドを実行している間に、他のスレッドがこのメソッドを実行しようとすると
現在 add() メソッドを実行しているスレッドが処理を終えるまで、待機することがわかります


synchronizedブロック

synchronized キーワードには、もう一つの使い方があります
単体のオブジェクトレベルで同期をとることが可能な synchronized ブロックです
メソッドの修飾子に指定する場合とことなり、特定の文に対して同期させることができます

ブロック型は、synchronizedキーワードを指定して
その直後に ( ) でオブジェクトを選択して { } で範囲を指定します

sinchronized ( obj ) { 処理ブロック }

objには、処理ブロックを実行している間はロックするオブジェクトを指定します
この文の効果によって、処理ブロックを実行している間
他のスレッドは、同一のオブジェクトで処理ブロックを実行することはできません
ロックを持つスレッドがブロックを抜け出し、ロックを解放するまで待機します
class Test extends Thread {
	SyncTest sync;
	public static void main(String args[]) {
		SyncTest sync = new SyncTest();
		
		for(int i = 0 ; i < 10 ; i++) {
			Test obj = new Test(sync);
			obj.start();	
		}
	}

	public Test(SyncTest sync) { 
		this.sync = sync;
	}
	public void run() {
		synchronized(sync) {
			sync.add();
		}
	}
}

class SyncTest {
	int var1, var2;
	void add() {
		var1++;
		System.out.println("処理1 : var1 = " + var1 + " : var2 = " + var2);

		try { Thread.sleep(100); }
		catch(InterruptedException err) { }

		var2++;
		System.out.println("処理2 : var1 = " + var1 + " : var2 = " + var2);
	}
}
このプログラムでは、SyncTest クラスのオブジェクトを作成し
複数のスレッドでこのオブジェクトを共有させ、add() メソッドを実行させています
Test クラスの run() メソッドでは、保有する SyncTest オブジェクトの add() の呼び出しに対し
他のスレッドが割り込まないように synchronized ブロックでオブジェクトをロックしています


sinchronized

メソッドをスレッドセーフにします
メソッドの修飾子として指定されたメソッドは
同時に2つ以上のスレッドから実行されることがなくなります

文として使用する場合は
直後に( )デ囲んで式を指定し、ブロック範囲を指定します
括弧内の式がオブジェクト型、配列型に評価された場合
ブロック実行前にその型がロックされます



前のページへ戻る次のページへ