List<String> list = Collections.synchronizedList(new ArrayList<String>());
synchronized (list) {
list.add("message");
}
ブロックは「同期(リスト){}」本当に必要ですか?
例に示すように同期する必要はありません。ただし、非常に重要なことですが、リストを反復処理する際にはリストを同期する必要があります(Javadocに記載されています)。
ユーザーは、返されたリストを反復処理するときに、返されたリストを手動で同期することが不可欠です。
List list = Collections.synchronizedList(new ArrayList()); ... synchronized(list) { Iterator i = list.iterator(); // Must be in synchronized block while (i.hasNext()) foo(i.next()); }
synchronized
ブロックの正確な内容に依存します。
ブロックがリストで単一のアトミック操作を実行する場合(例のように)、synchronized
は不要です。
ブロックがリストに対して複数の操作を実行する場合-および複合操作の間ロックを維持する必要がある-synchronized
はnot余分な。これの1つの一般的な例は、リストの繰り返しです。
Collections.synchronizedList addメソッドの基礎となるコードは次のとおりです。
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
したがって、あなたの例では、同期を追加する必要はありません。
また、Collections.sort()などのイテレータを使用するメソッドも、同期ブロック内にカプセル化する必要があることに注意してください。
これを読む Oracle Doc
「返されたリストを反復処理する場合、ユーザーは返されたリストを手動で同期することが必須です」
他の人が言及したように、同期されたコレクションはthread-safeですが、これらのコレクションに対する複合アクションはデフォルトでスレッドセーフであるとは限りません。
JCIPによれば、一般的な複合アクションは
OPの同期コードブロックは複合アクションではないため、追加しても追加しなくても違いはありません。
JCIPの例を使用して、ロックを使用して複合アクションを保護する必要がある理由を明確にするために、少し変更してみましょう。
Collections.synchronizedList
でラップされた同じコレクションlist
で動作する2つのメソッドがあります
public Object getLast(List<String> list){
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
public void deleteLast(List<String> list){
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
メソッドgetLast
とdeleteLast
が2つの異なるスレッドによって同時に呼び出された場合、以下のインターリーブが発生し、getLast
はArrayIndexOutOfBoundsException
をスローします。現在のlastIndex
が10であると仮定します。
スレッドA(deleteLast)->削除
スレッドB(getLast)--------------------> get
スレッドA remove
は、スレッドBのget
操作の前の要素です。したがって、スレッドBは、list.get
メソッドを呼び出すために、lastIndex
として10を使用します。同時に問題が発生します。