Collections.synchronizedList和synchronized

时间:2012-02-27 16:08:38

标签: java collections synchronization

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

块“synchronized(list){}”真的需要吗?

6 个答案:

答案 0 :(得分:93)

您不需要像放入示例中那样进行同步。但是,非常重要的是,在迭代时需要在列表周围进行同步(如Javadoc中所述):

  

用户必须手动同步返回的内容   迭代时列出:

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

答案 1 :(得分:29)

这取决于synchronized块的确切内容:

  1. 如果块在列表上执行单个原子操作(如您的示例所示),则synchronized是多余的。

  2. 如果块在列表上执行多项操作 - 并且需要在复合操作的持续时间内保持锁定 - 那么synchronized不是多余的。一个常见的例子是迭代列表。

答案 2 :(得分:20)

Collections.synchronizedList add方法的基础代码是:

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

因此,在您的示例中,不需要添加同步。

答案 3 :(得分:16)

另外需要注意的是,任何使用Iterators的方法(例如Collections.sort())也需要封装在synchronized块中。

答案 4 :(得分:7)

阅读此Oracle Doc

它说“当迭代时,用户必须手动同步返回的列表”

答案 5 :(得分:1)

与其他人提到的一样,同步集合是线程安全的,但默认情况下,对这些集合的复合操作不保证是线程安全的。

根据JCIP,常见的复合行动可以

  • 迭代
  • 导航
  • 放-IF-缺席
  • 签然后-行为

OP的同步代码块不是复合动作,因此无论是否添加它都没有区别。

让我们以JCIP为例,稍加修改,以澄清为什么有必要用锁来保护复合动作。

有两种方法对list

包裹的同一集合Collections.synchronizedList进行操作
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,则可能会发生以下交错,getLast会抛出ArrayIndexOutOfBoundsException。假设当前lastIndex为10.

主题A(deleteLast) - &gt;除去
线程B(getLast)--------------------&gt;得到

线程A remove线程B中get操作之前的元素。因此,线程B仍然使用10作为lastIndex来调用list.get方法,它会导致并发问题。