回答編集履歴

2

補足説明(追記)

2020/04/01 15:18

投稿

xebme
xebme

スコア1083

test CHANGED
@@ -65,3 +65,207 @@
65
65
 
66
66
 
67
67
  [TSM03-J. 初期化が完了していないオブジェクトを公開しない](https://www.jpcert.or.jp/java-rules/tsm03-j.html)
68
+
69
+
70
+
71
+ ###
72
+
73
+ **補足説明(追記)**
74
+
75
+
76
+
77
+ 訂正:(putfieldでなくローカル変数への格納istoreでもよい)は取り下げます。putfieldだけに限定します。
78
+
79
+
80
+
81
+ **コンストラクターの動作**
82
+
83
+
84
+
85
+ コンストラクターはスレッドセーフではないの意味は、まず言語仕様を引用します。
86
+
87
+ > There is no practical need for a constructor to be synchronized, because it would lock the object under construction, which is normally not made available to other threads until all constructors for the object have completed their work.
88
+
89
+
90
+
91
+
92
+
93
+ コンストラクターに`synchronized`修飾が必要ない理由説明。インスタンスが生成される一般的なシナリオでは、コンストラクターを実行するスレッドは、当該オブジェクトの参照値を占有しており、コンストラクターが完了するまで他のスレッドに公開することがないから。(`synchronized`できないが、)ロックしているのと同じ意味だと解釈します。
94
+
95
+
96
+
97
+ 上のバイトコードにあてはめると、ステップ1〜4までが同一スレッドで実行されるため、ステップ4で他のスレッドに公開される時は、ステップ3が完了してることが保障される。と読むことができます。
98
+
99
+
100
+
101
+ しかし、一般的なシナリオからの逸脱があり、ステップ3の途中を他のスレッドから観ることができることがJPCERTの記事で説明されています。
102
+
103
+
104
+
105
+ **バイトコードのステップごとの状態**
106
+
107
+
108
+
109
+ バイトコードのステップごとにオブジェクトの状態を考えます。
110
+
111
+
112
+
113
+ - ステップ1〜ステップ2
114
+
115
+ オブジェクトの領域をアロケート済み。変数にはすべてビット0が設定されている。参照値は取得済。
116
+
117
+ - ステップ3
118
+
119
+ コンストラクターメソッド内でメンバ変数の初期値を設定。完了するまでに中間状態がある。
120
+
121
+ - ステップ4
122
+
123
+ オブジェクトのメンバ変数は完全に設定済み。
124
+
125
+
126
+
127
+ **JPCERTの記事**
128
+
129
+
130
+
131
+ 「コンパイラがバイトコードのステップ3と4を入れ替えてもよい」との記述があります。ステップ4が先なら、変数にすべてビット0が設定されている状態を他のスレッドに見せることができます。これは質問の反例になりませんか。
132
+
133
+
134
+
135
+ **JPCERTの記事(逸出)**
136
+
137
+
138
+
139
+ [TSM01-J. オブジェクトの構築時にthis参照を逸出させない](https://www.jpcert.or.jp/java-rules/tsm01-j.html) これも質問の反例になりうると思います。
140
+
141
+
142
+
143
+ **わたしの確認コード(逸出)**
144
+
145
+
146
+
147
+ static フィールドへの参照値の逸出。`CountDownLatch`を利用した逸出確認。メモリ整合性が保証されるので初期化前後のオブジェクトの内部状態を確実に見ることができます。以下は逸出させる側のオブジェクト。
148
+
149
+
150
+
151
+ ```Java
152
+
153
+ public class Escaped {
154
+
155
+
156
+
157
+ final int i;
158
+
159
+ final String s;
160
+
161
+
162
+
163
+ {
164
+
165
+ EscapedHolder.escaped = this; // thisの逸出(escape)
166
+
167
+ EscapedHolder.startSignal.countDown(); // 待ちスレッドを解放
168
+
169
+ try {
170
+
171
+ EscapedHolder.doneSignal.await(); // 待機
172
+
173
+ } catch (InterruptedException e) {
174
+
175
+ e.printStackTrace();
176
+
177
+ }
178
+
179
+ i = 100;
180
+
181
+ s = "Escape_";
182
+
183
+ }
184
+
185
+
186
+
187
+ public Escaped() {
188
+
189
+ super();
190
+
191
+ }
192
+
193
+
194
+
195
+ }
196
+
197
+ ```
198
+
199
+
200
+
201
+ staticフィールドで参照値の逸出をうける。逸出したオブジェクトの内容をスレッドで確認する。
202
+
203
+
204
+
205
+ ```Java
206
+
207
+ import java.util.concurrent.CountDownLatch;
208
+
209
+
210
+
211
+ public class EscapedHolder {
212
+
213
+
214
+
215
+ static Escaped escaped; // 逸出先
216
+
217
+ static final CountDownLatch startSignal = new CountDownLatch(1);
218
+
219
+ static final CountDownLatch doneSignal = new CountDownLatch(1);
220
+
221
+
222
+
223
+ public static void main(String[] args) {
224
+
225
+
226
+
227
+ Thread t = new Thread(() -> {
228
+
229
+ try {
230
+
231
+ startSignal.await();
232
+
233
+ } catch (InterruptedException e) {
234
+
235
+ e.printStackTrace();
236
+
237
+ }
238
+
239
+ System.out.println("escaped : i = " + EscapedHolder.escaped.i + " s = " + EscapedHolder.escaped.s);
240
+
241
+ doneSignal.countDown();
242
+
243
+ });
244
+
245
+ t.setDaemon(true);
246
+
247
+ t.start();
248
+
249
+
250
+
251
+ System.out.println(">>>>>> before construction. >>>>>>");
252
+
253
+ Escaped instance = new Escaped();
254
+
255
+ System.out.println(">>>>>> after construction. >>>>>>");
256
+
257
+
258
+
259
+ System.out.println("instance : i = " + instance.i + " s = '" + instance.s + "'");
260
+
261
+ System.out.println("escaped : i = " + EscapedHolder.escaped.i + " s = '" + EscapedHolder.escaped.s + "'");
262
+
263
+
264
+
265
+ }
266
+
267
+
268
+
269
+ }
270
+
271
+ ```

1

putfieldでなくてローカル変数への格納istoreでもよいことを注釈

2020/04/01 15:17

投稿

xebme
xebme

スコア1083

test CHANGED
@@ -56,7 +56,7 @@
56
56
 
57
57
  この処理過程で、super()の連鎖によってクラスごとにインスタンスの初期値が設定される。
58
58
 
59
- - メンバ変数への参照値の格納
59
+ - メンバ変数への参照値の格納 (putfieldでなくローカル変数への格納istoreでもよい)
60
60
 
61
61
 
62
62