次のコードを検討し、list が同期された List であると仮定します。
List list = Collections.synchronizedList(new ArrayList());
if(!list.contains(element)){
list.add(element)
}
上記のコード フラグメントは、完全にスレッド セーフにするために外部で同期する (ロックで保護する) 必要があることを知っています。
次のコードを検討し、list が同期された List であると仮定します。
List list = Collections.synchronizedList(new ArrayList());
if(!list.contains(element)){
list.add(element)
}
上記のコード フラグメントは、完全にスレッド セーフにするために外部で同期する (ロックで保護する) 必要があることを知っています。
実はたくさんあります。が評価されている間にリストが変更される可能性がありcontains
ます。到達するまでに、add
誰かがその要素を追加し、再度追加する可能性があります。さらに、同期がないと、他のスレッドによる書き込みがスレッドによって部分的に監視されるか、順不同で監視されるか、まったく監視されない可能性があるため、全体が奇妙な方法で崩壊する可能性があります。
contains
とがそれ自体でアトミック (同期化) されている場合add
、 と の呼び出しの間に明確に定義された競合が少なくとも 1 つcontains
ありadd
ます。
2 つのスレッドがチェックを実行し、両方が条件ステートメントに入ると仮定します。次に、両方が同じ要素をリストに追加しますが、これは意図した動作ではありません。そうじゃない?
2 つのスレッドがあるとします。
A: if(!list.contains(element)){ // false
B: if(!list.contains(element)){ // false
A: list.add(element) // add once
B: list.add(element) // add a second time.
もちろん、簡単な解決策は Set を使用することです。例えば
Set<E> set = Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());
set.add(element);
リストを取得してから再保存するまでの間に競合状態が発生します(ファイルの例に保存する場合)。
例: ファイルには A が含まれています
読み取りファイルには AC が含まれ、スレッド 1 が行った作業は失われます
list.contains(element)
私の見方では、 yieldがfalseで、別のスレッドがその後要素を追加するが、最初のスレッドで追加する呼び出しの前に、競合状態が発生します。