スレッドでsynchronizedを使う
こんにちは。「Javaを復習する初心者」です。今回は複数のスレッドから同じオブジェクトを操作することをしました。synchronizedを使うことで処理を同期させるということをやりました。
StringBuilderクラスという可変の文字列を扱うクラスがあります。append()メソッドで文字列を追加することができます。このクラスなんですが、API仕様書には「同期化は保証されません」という記載があります。そこで、このクラスのインスタンスに対し、2つのスレッドからappend()メソッドを呼び出す実験をしました。
テスト用メソッド
以下のようにテスト用メソッドを作成しました。
private static int test1() { StringBuilder sb = new StringBuilder(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { sb.append("a"); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { sb.append("a"); } }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } return sb.length(); }
上記でThread#startメソッドはスレッドを開始するメソッドです。Thread#joinメソッドはスレッドの終了するのを待機するメソッドです。待機しない場合、先にtest1メソッドのreturn文が実行されてしまいます。
StringBuilder型変数sbに対して、2つのスレッドt1とt2がappend()メソッドを呼び出してます。それぞれ1000回ずつ呼び出しています。このメソッドの返却値はsbの長さです。StringBuilderは同期化は保証されないため、sbの長さが2000より小さい値になる可能性があります。このことを確認するのが目的です。
出力
以下のように、このテストメソッドを10回呼び出し、返却された値を出力してみました。
for (int i = 1; i <= 10; i++) { System.out.printf("%2d回目: %3d", i, test1()); System.out.println(); }
結果は以下です。
1回目: 2000 2回目: 2000 3回目: 2000 4回目: 1925 5回目: 2000 6回目: 1987 7回目: 1970 8回目: 2000 9回目: 2000 10回目: 2000
上記結果は何回か実行した中での結果の一つです。結果が2000にならない場合があります。これは2つのスレッドがほぼ同時にappend()メソッドを呼び出した時があるということです。
上記結果はエラーの出力がありませんが、時々、ArrayIndexOutOfBoundsExceptionが発生する場合がありました。
非同期の場合
さて、上記のように同期していない場合は期待動作と異なる可能性があります。なのでsynchronizedキーワードを使います。
以下、synchronizedを追加した場合です。
private static int test2() { StringBuilder sb = new StringBuilder(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { synchronized (sb) { sb.append("a"); } } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { synchronized (sb) { sb.append("a"); } } }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } return sb.length(); }
synchronized (sb) {}と記述することで、sbをロックして処理することできます。同時にappend()メソッドが実行されることがなるなるため、実効結果は以下のようになります。
1回目: 2000 2回目: 2000 3回目: 2000 4回目: 2000 5回目: 2000 6回目: 2000 7回目: 2000 8回目: 2000 9回目: 2000 10回目: 2000
何度か試しましたが、必ず2000が出力されてました。