teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

1

不安定な理由を追加

2016/04/30 05:36

投稿

raccy
raccy

スコア21784

answer CHANGED
@@ -47,4 +47,42 @@
47
47
  50045045045040
48
48
  19.99 real 66.79 user 0.90 sys
49
49
  ```
50
- CountPalはswordoneさんのオリジナルコード、CountPal2はKiyoshiMotokiさんのコード、CountPal3が上のコードです。KiyoshiMotokiさんのコードが時々違う値を返すのは私にもわからないです。オリジナルのコードも値が大きいと時々違う値になるようです。脆弱性がまた出たからJava 1.8.0_92にあげたばかりなんですけど、なぜなんでしょうかね?
50
+ CountPalはswordoneさんのオリジナルコード、CountPal2はKiyoshiMotokiさんのコード、CountPal3が上のコードです。KiyoshiMotokiさんのコードが時々違う値を返すのは私にもわからないです。オリジナルのコードも値が大きいと時々違う値になるようです。脆弱性がまた出たからJava 1.8.0_92にあげたばかりなんですけど、なぜなんでしょうかね?
51
+
52
+ ---
53
+
54
+ ###オリジナルやKiyoshiMotokiさんのコードが不安定になる理由(たぶん、でも自信無し)
55
+ ```Java
56
+ synchronized (count) {
57
+ count += total;
58
+ }
59
+ ```
60
+ 原因は上の部分ですが、`count`がLongだからではなく、`count`が新しいオブジェクトに置き換わっているからだと思われます。上のコードは、オートボクシングせずに、かつ、`+=`の糖衣構文も分解すると下記になります。
61
+ ```Java
62
+ synchronized (count) {
63
+ count = new Long(count.longValue() + total)
64
+ }
65
+ ```
66
+ そう、`count`には**新しいオブジェクト**が代入されます。この新しい`count`は、`synchronized `によって現在ロックされているオブジェクトではありません。でも既に置き換わっているから大丈夫?と思うかも知れませんが、そうではありません。他のスレッドが`count`を評価済みの場合は排他制御が失敗します。例えば、スレッドth1、th2、th3と三つある場合を考えます。現在の`count`をct1として、新しく作られる度にct2、ct3とすると次のような動作が発生すると考えらます。
67
+
68
+ スレッド(ロックオブジェクト): 処理
69
+ th1: ct1でsynchronized -> ct1のロック成功
70
+ th2: ct1でsynchronized -> th1がロック済みのため、ブロック
71
+ th1(ct1): `count`からct1の値を取得
72
+ th1(ct1): 取得した値(ct1)からct2を生成
73
+ th1(ct1): `count`にct2を代入
74
+ th1: ct1のロック解除
75
+ th2: ct1のロックが解除されたのでブロック解除、**ct1**のロック成功
76
+ th3: ct2でsynchronized -> **ct2**のロック成功 (th3が見に行くときは`count`はct2になっている)
77
+ th2(ct1): `count`からct2の値を取得
78
+ th3(ct2): `count`からct2の値を取得
79
+ th2(ct1): 取得した値(ct2)からct3を生成
80
+ th3(ct2): 取得した値(ct2)からct3'を生成
81
+ th2(ct1): `count`にct3を代入
82
+ th3(ct2): `count`にct3'を代入 // th2でct3の代入は無かったことに
83
+ th2: ct1のロック解除
84
+ th3: ct2のロック解除
85
+
86
+ th2の処理は無かったことにされ、その分すくない結果となります。重要なのは、`synchronized(count)`でロックが取れずブロックされた時点で、`count`の評価が終わっており、**ブロック解除された後に再評価されない**(上でいうと、ct2を取得し直さずに、ct1を使おうとする)と言う点です。そのため、待たされていたスレッドは古いオブジェクトを使ってロックをかけてしまい、新しいオブジェクトでロックをしようとする他のスレッドと処理が重複します。
87
+
88
+ なお、`private static Long lock = 0L;`とロック用に`lock`を作った場合は問題は起きませんでしたので、Longオブジェクトからと言う理由ではないと思われます。synchronizedの中で代入する形であれば、Mutable/Immutableおよびラッパークラスかどうかに関係なく発生する可能性があると思われます。