この演習は、キャシー・セイラとバート・ベイツによる SCJP からそのまま引用したものです。
コード ブロックの同期
この演習では、コード ブロックの同期を試みます。そのコード ブロック内でオブジェクトのロックを取得し、コード ブロックの実行中に他のスレッドがオブジェクトを変更できないようにします。すべて同じオブジェクトを操作しようとする 3 つのスレッドを作成します。各スレッドは 1 つの文字を 100 回出力し、その文字を 1 ずつ増やします。使用するオブジェクトは StringBuffer です。
String オブジェクトで同期することはできますが、作成された文字列は変更できないため、新しい String オブジェクトを生成せずに文字をインクリメントすることはできません。最終的な出力には、100 個の As、100 個の B、および 100 個の C がすべて切れ目のない行で含まれている必要があります。
- クラスを作成し、Thread クラスを拡張します。
- Thread の run() メソッドをオーバーライドします。これは、コードの同期ブロックが移動する場所です。
- 3 つのスレッド オブジェクトが同じオブジェクトを共有するには、引数で StringBuffer オブジェクトを受け入れるコンストラクターを作成する必要があります。
- 同期されたコード ブロックは、手順 3 から StringBuffer オブジェクトのロックを取得します。
- ブロック内で、StringBuffer を 100 回出力してから、StringBuffer 内の文字をインクリメントします。これに役立つ StringBuffer メソッドについては、第 6 章を参照してください。
- 最後に、main() メソッドで、文字 A を使用して単一の StringBuffer オブジェクトを作成し、クラスの 3 つのインスタンスを作成して、3 つすべてを開始します。
上記の演習用に以下のクラスを作成しました (100 文字ではなく、10 文字を印刷しています)。
class MySyncBlockTest extends Thread {
StringBuffer sb;
MySyncBlockTest(StringBuffer sb) {
this.sb=sb;
}
public static void main (String args[]) {
StringBuffer sb = new StringBuffer("A");
MySyncBlockTest t1 = new MySyncBlockTest(sb);
MySyncBlockTest t2 = new MySyncBlockTest(sb);
MySyncBlockTest t3 = new MySyncBlockTest(sb);
t1.start();
t2.start();
t3.start();
}
public void run() {
synchronized(this) {
for (int i=0; i<10; i++) {
System.out.print(sb);
}
System.out.println("");
if (sb.charAt(0)=='A')
sb.setCharAt(0, 'B');
else
sb.setCharAt(0, 'C');
}
}
}
次のような出力 (10 As、10 B、10 C) を期待していましたが、取得できませんでした。
AAAAAAAAAA
BBBBBBBBBB
CCCCCCCCCC
代わりに、次のようなさまざまな出力が得られました。これは、3 つのスレッドが他のスレッドが終了する前にループに入る機会を得ているためです。
AAAAAAAAAAAAAAAAAA
ABB
ACCCCCCCC
私の質問は、run メソッドの同期ブロックが機能しないのはなぜですか?