synchronized ブロックRevised: Sep./01st/2004; since: Mar./23rd/2002
前節で synchronized 修飾子を紹介しました。これはメンバーをロックして多重アクセスを防ぐものです。同様の発想で、制御の特定の部分をブロック化して、その部分だけロックを掛けることも出来ます。このようなブロックを synchronized ブロックと呼びます。
クラス Customer のメソッド addCust() は、引数で受け取った文字列 name が、顧客名簿 CustList 型オブジェクト list に存在するかどうか調べて list.isCust(name) 、存在すれば true を返し、存在しなければ名簿に追加 list.addCust(name) して false を返します。
import java.io.*;
// 顧客名簿オブジェクト
class CustList {
// 顧客名簿ファイルの抽象パス名オブジェクト
private File list = new File("customers.txt");
// 顧客名簿に名前が存在するかどうかチェック
public boolean isCust(String name) {
try {
// 入力ストリームのバッファリング
BufferedReader br = new BufferedReader(new FileReader(list));
// 一行読み込んで str に代入し、
// ファイル終了まで繰り返す。
String str = "";
while ((str = br.readLine()) != null) {
if (str.indexOf(name) != -1) {
// 出力ストリームのクローズ
br.close();
// 名前が見付かれば true を返す
return true;
}
}
// 出力ストリームのクローズ
br.close();
} catch (Exception e) {
System.out.println(e);
}
// ファイル終了まで名前が見付からなかった場合に、
// false を返す。
return false;
}
// 顧客名簿への書き出し
public void addCust(String name) {
try {
// 出力ストリームのバッファリング
BufferedWriter bw = new BufferedWriter(new FileWriter("customers.txt", true));
bw.write(name);
bw.newLine();
bw.flush();
bw.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
class Customer {
// 顧客名簿ファイルの抽象パス名オブジェクト
CustList list;
// コンストラクタ
Customer(CustList obj) {
list = obj;
}
boolean addCust(String name) {
if (list.isCust(name)) {
return false;
} else {
// 作為的に矛盾を起こす為の sleep
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
list.addCust(name);
}
return true;
}
}
addCust() では、list.isCust(name) が false になるとき(顧客名が見つからなかったとき)、最低でも2秒間停止するように sleep() させています。2秒を超えると、CPUのディスパッチの待機状態になります。シングルスレッドでは単に停止するだけですが、マルチスレッドの場合、ここで停止している間に他のスレッドが追加してしまうなどの不整合が発生するタイミングを広げることになります。
このクラスを使うコントロール・クラスは次のようにかけます:
CustTest.java
class CustTest {
public static void main(String[] args) {
String name="";
for (int i = 0; i < args.length; i++) {
name = name + " " + args[i];
}
CustList list = new CustList();
Customer cust = new Customer(list);
System.out.println(cust.addCust(name));
}
}
コマンドライン:
C:\Java\Thread>javac CustTest.java C:\Java\Thread>java CustTest sugai true C:\Java\Thread>java CustTest sugai manabu true C:\Java\Thread>java CustTest 菅井 学 true C:\Java\Thread>java CustTest sugai false C:\Java\Thread>
上の実行例では、 customers.txt は次のようになります:
sugai sugai manabu 菅井 学
これをマルチ・スレッドで走らせるコントロール・クラスを次のように書いたとします:
CustTest2.java
class CustName implements Runnable {
Customer cust;
String name;
CustName(Customer obj, String str) {
cust = obj;
name = str;
}
public void run() {
System.out.println(cust.addCust(name));
}
}
class CustTest2 {
public static void main(String[] args) {
CustList list = new CustList();
Customer customer = new Customer(list);
CustName[] cust = new CustName[5];
cust[0] = new CustName(customer, "sugai");
cust[1] = new CustName(customer, "suzuki");
cust[2] = new CustName(customer, "sugai");
cust[3] = new CustName(customer, "sato");
cust[4] = new CustName(customer, "sugai");
// スレッドの配列の宣言
Thread[] thres = new Thread[5];
// 5つのスレッドの作成
for (int i=0; i<5; i++) {
// Runnable オブジェクトをスレッドに委譲
thres[i] = new Thread(cust[i]);
// スレッドの開始
thres[i].start();
}
}
}
C:\Java\Thread>javac CustTest2.java C:\Java\Thread>java CustTest2 true true true true true C:\Java\Thread>
この結果、 customers.txt は次のようになります:
sugai suzuki sato sugai sugai
同じ名前のエントリーが複数作成されています。この矛盾は、一つのスレッドが書き込む前に、次のスレッドが読み込んでサーチしてしまうために、エントリーがないものと思って書き込んでしまうために発生します。
synchronized 修飾子により同期指定したマルチスレッドプログラムこれを回避する為には、前節の synchronized 修飾子では次のように修正することになります。
class Customer {
CustList list;
Customer(CustList obj) {
list = obj;
}
synchronized boolean addCust(String name) {
if (list.isCust(name)) {
return false;
} else {
// 作為的に矛盾を起こす為の sleep
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
list.addCust(name);
}
return true;
}
}
これで次のように矛盾は回避されます:
C:\Java\Thread>javac CustTest2.java C:\Java\Thread>java CustTest2 true true false true false C:\Java\Thread>
結果として、 customers.txt は次のようにまります:
sugai suzuki sato
書き込みメソッドが同期指定された為に、重複書き込みがなくなりました。
synchronized ブロックにより同期指定したマルチスレッドプログラムしかしながら、 Customer クラスで保護したいのは、 addCust() メソッド全てではなく、その一部のコードであるのも事実です。また、同期保護したいのは Customer クラス型オブジェクトではなく、そこで利用する CustList クラス型オブジェクト list の方です。
これらの2点を考慮すると、 Customer クラスは次のように書くことで同期保護できます。
class Customer {
CustList list;
Customer(CustList obj) {
list = obj;
}
boolean addCust(String name) {
synchronized(list) {
if (list.isCust(name)) {
return false;
} else {
// 作為的に矛盾を起こす為の sleep
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
list.addCust(name);
}
}
return true;
}
}
このとき、 customer.txt を白紙にした状態での実行結果は次のようになります。
C:\Java\Thread>javac CustTest2.java C:\Java\Thread>java CustTest2 true false true false true C:\Java\Thread>
sugai suzuki sato