回答編集履歴

1

不安定な理由を追加

2016/04/30 05:36

投稿

raccy
raccy

スコア21735

test CHANGED
@@ -97,3 +97,79 @@
97
97
  ```
98
98
 
99
99
  CountPalはswordoneさんのオリジナルコード、CountPal2はKiyoshiMotokiさんのコード、CountPal3が上のコードです。KiyoshiMotokiさんのコードが時々違う値を返すのは私にもわからないです。オリジナルのコードも値が大きいと時々違う値になるようです。脆弱性がまた出たからJava 1.8.0_92にあげたばかりなんですけど、なぜなんでしょうかね?
100
+
101
+
102
+
103
+ ---
104
+
105
+
106
+
107
+ ###オリジナルやKiyoshiMotokiさんのコードが不安定になる理由(たぶん、でも自信無し)
108
+
109
+ ```Java
110
+
111
+ synchronized (count) {
112
+
113
+ count += total;
114
+
115
+ }
116
+
117
+ ```
118
+
119
+ 原因は上の部分ですが、`count`がLongだからではなく、`count`が新しいオブジェクトに置き換わっているからだと思われます。上のコードは、オートボクシングせずに、かつ、`+=`の糖衣構文も分解すると下記になります。
120
+
121
+ ```Java
122
+
123
+ synchronized (count) {
124
+
125
+ count = new Long(count.longValue() + total)
126
+
127
+ }
128
+
129
+ ```
130
+
131
+ そう、`count`には**新しいオブジェクト**が代入されます。この新しい`count`は、`synchronized `によって現在ロックされているオブジェクトではありません。でも既に置き換わっているから大丈夫?と思うかも知れませんが、そうではありません。他のスレッドが`count`を評価済みの場合は排他制御が失敗します。例えば、スレッドth1、th2、th3と三つある場合を考えます。現在の`count`をct1として、新しく作られる度にct2、ct3とすると次のような動作が発生すると考えらます。
132
+
133
+
134
+
135
+ スレッド(ロックオブジェクト): 処理
136
+
137
+ th1: ct1でsynchronized -> ct1のロック成功
138
+
139
+ th2: ct1でsynchronized -> th1がロック済みのため、ブロック
140
+
141
+ th1(ct1): `count`からct1の値を取得
142
+
143
+ th1(ct1): 取得した値(ct1)からct2を生成
144
+
145
+ th1(ct1): `count`にct2を代入
146
+
147
+ th1: ct1のロック解除
148
+
149
+ th2: ct1のロックが解除されたのでブロック解除、**ct1**のロック成功
150
+
151
+ th3: ct2でsynchronized -> **ct2**のロック成功 (th3が見に行くときは`count`はct2になっている)
152
+
153
+ th2(ct1): `count`からct2の値を取得
154
+
155
+ th3(ct2): `count`からct2の値を取得
156
+
157
+ th2(ct1): 取得した値(ct2)からct3を生成
158
+
159
+ th3(ct2): 取得した値(ct2)からct3'を生成
160
+
161
+ th2(ct1): `count`にct3を代入
162
+
163
+ th3(ct2): `count`にct3'を代入 // th2でct3の代入は無かったことに
164
+
165
+ th2: ct1のロック解除
166
+
167
+ th3: ct2のロック解除
168
+
169
+
170
+
171
+ th2の処理は無かったことにされ、その分すくない結果となります。重要なのは、`synchronized(count)`でロックが取れずブロックされた時点で、`count`の評価が終わっており、**ブロック解除された後に再評価されない**(上でいうと、ct2を取得し直さずに、ct1を使おうとする)と言う点です。そのため、待たされていたスレッドは古いオブジェクトを使ってロックをかけてしまい、新しいオブジェクトでロックをしようとする他のスレッドと処理が重複します。
172
+
173
+
174
+
175
+ なお、`private static Long lock = 0L;`とロック用に`lock`を作った場合は問題は起きませんでしたので、Longオブジェクトからと言う理由ではないと思われます。synchronizedの中で代入する形であれば、Mutable/Immutableおよびラッパークラスかどうかに関係なく発生する可能性があると思われます。