web-dev-qa-db-ja.com

Collections.synchronizedListおよびsynchronized

List<String> list = Collections.synchronizedList(new ArrayList<String>());
synchronized (list) {
    list.add("message");
}

ブロックは「同期(リスト){}」本当に必要ですか?

65
romsky

例に示すように同期する必要はありません。ただし、非常に重要なことですが、リストを反復処理する際にはリストを同期する必要があります(Javadocに記載されています)。

ユーザーは、返されたリストを反復処理するときに、返されたリストを手動で同期することが不可欠です。

List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list) {
    Iterator i = list.iterator(); // Must be in synchronized block
    while (i.hasNext())
        foo(i.next());   
}
94
Sam Goldberg

synchronizedブロックの正確な内容に依存します。

  1. ブロックがリストで単一のアトミック操作を実行する場合(例のように)、synchronizedは不要です。

  2. ブロックがリストに対して複数の操作を実行する場合-および複合操作の間ロックを維持する必要がある-synchronizednot余分な。これの1つの一般的な例は、リストの繰り返しです。

29
NPE

Collections.synchronizedList addメソッドの基礎となるコードは次のとおりです。

public void add(int index, E element) {
    synchronized (mutex) {list.add(index, element);}
}

したがって、あなたの例では、同期を追加する必要はありません。

21
assylias

また、Collections.sort()などのイテレータを使用するメソッドも、同期ブロック内にカプセル化する必要があることに注意してください。

16
jpegjpg

これを読む Oracle Doc

「返されたリストを反復処理する場合、ユーザーは返されたリストを手動で同期することが必須です」

7
snan

他の人が言及したように、同期されたコレクションは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);
}

メソッドgetLastdeleteLastが2つの異なるスレッドによって同時に呼び出された場合、以下のインターリーブが発生し、getLastArrayIndexOutOfBoundsExceptionをスローします。現在のlastIndexが10であると仮定します。

スレッドA(deleteLast)->削除
スレッドB(getLast)--------------------> get

スレッドA removeは、スレッドBのget操作の前の要素です。したがって、スレッドBは、list.getメソッドを呼び出すために、lastIndexとして10を使用します。同時に問題が発生します。

1
Gearon