文字の洪水に溺れながら

人生初心者、でも人生のハードモードぐらいを生き抜きたい人間。

Java:排他制御がかからないと失敗するサンプルコード

必要にかられてせっせこjavaを勉強しています。

最近はマルチスレッドの学習を進めているのですが、
理解がイマイチなので今まで理解したところをメモ。
テキストは独習JAVA 9-3

テキストにはサンプルコードとして正しいものしか
書かれていないので理解に苦しみました。
なので駄目な方も書いてみたらだいぶ理解が進みました。
やはり詰まったら何事も手を動かすことは重要だね。

以下のコードでSynchronizedをつけたコメントアウトしてる方のコードは問題なく値は定まるが、つけていないほうで実行すると値は定まらない。

何故ならばdepositメソッドがロックが掛かっていないと、並列処理中に2回やったはずなのに1回分がなかったことにされる事が発生してしまうから。
詳細はパーフェクトJavaの図16.3で書かれている。

package main;


public class Account {
	private int balance = 0;

	void deposit(int amount){
		//	synchronized void deposit(int amount){

		balance += amount;
	}

	int getBalance(){
		return balance;
	}
}


class Customer extends Thread {
	Account account;

	Customer(Account account){
		this.account = account;
	}

	public void run() {
		try {
			for (int i = 0; i < 10000; i++) {
				account.deposit(10);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

class BankDemo{
	private final static int NUMCUSTOMERS = 10;	
	public static void main(String args[]){

		//口座の作成
		Account account = new Account();

		//スレッドの作成と起動
		Customer customers[] = new Customer[NUMCUSTOMERS];
		for (int i = 0; i < NUMCUSTOMERS; i++) {
			customers[i] = new Customer(account);
			customers[i].start();
		}

		//スレッドの完了を待機する
		for (int i = 0; i < NUMCUSTOMERS; i++) {
			try {
				customers[i].join();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		//口座の残高を表示する
		System.out.println(account.getBalance());
	}
}

いまいちまだわかっていないのはSynchronizedブロックを利用する際にどこのスコープでブロック指定を行えばいいのかという点。
たとえば、上記のコード内のmain関数を

	public static void main(String args[]){

		//口座の作成
		Account account = new Account();

		synchronized(account){
			//スレッドの作成と起動
			Customer customers[] = new Customer[NUMCUSTOMERS];
			for (int i = 0; i < NUMCUSTOMERS; i++) {
				customers[i] = new Customer(account);
				customers[i].start();				
			}

			//スレッドの完了を待機する
			for (int i = 0; i < NUMCUSTOMERS; i++) {
				try {
					customers[i].join();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}

		//口座の残高を表示する
		System.out.println(account.getBalance());
	}

とaccountをロックしていても値は定まらない。
だけどrun()の方を以下のようにSynchronizedブロックを行うと値が定まる。

class Customer extends Thread {
	Account account;

	Customer(Account account){
		this.account = account;
	}

	public void run() {
		try {
			for (int i = 0; i < 10000; i++) {
				synchronized(account){
					account.deposit(10);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

ここら辺を説明できるようにしておくこと