2012-02-27 48 views
62
List<String> list = Collections.synchronizedList(new ArrayList<String>()); 
synchronized (list) { 
    list.add("message"); 
} 

块“synchronized(list){}”真的需要在这里吗?Collections.synchronizedList和同步

回答

19

底层代码Collections.synchronizedList添加方法是:

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

因此,在您例如,它不需要添加同步。

+0

它将使用什么样的互斥体? – anoopelias 2013-11-07 10:48:47

+2

互斥量是javadoc中记录的集合本身(this)。 – assylias 2013-11-07 12:24:03

89

您不需要按照您的示例进行同步。然而,非常重要的,你需要的时候你迭代它(如在Javadoc注)周围列表同步:

当务之急是用户迭代它时,返回 名单上手动同步:

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

链接到上述语句:[docs](http://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#synchronizedList(java.util.List)) – 2013-02-13 09:28:05

+1

常见用法像这样的一个同步集合的情况是添加到多个线程的列表中,但只在所有任务完成时才在最后进行迭代。 在这种情况下,我没有看到任何理由在迭代周围进行同步,因为它是从单个线程完成的。 – Desty 2013-08-06 12:30:43

+3

只要您知道在迭代过程中列表未被更新,您就不需要同步它。不过,我不知道我是否会将该用例描述为“普通”。 (我曾经见过ConcurrentModificationException不止一次。)在你提到的用例中,当另一个线程正在迭代时,什么阻止线程再次添加到列表中?当其他线程完成更新列表时,迭代线程如何“知道”? – 2013-08-06 13:50:55

28

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

  1. 如果块执行在列表中是一个单一的原子操作(如你的例子),​​是多余的。

  2. 如果块执行列表上的多个操作 - 和需要保持的锁的化合物操作的持续时间 - 那么​​是多余的。一个常见的例子是迭代列表。

16

同样重要的是要注意,任何使用迭代器(例如Collections.sort())的方法也需要封装在同步块中。

+0

,排序方法已添加到List接口,并且此方法已在互斥锁上同步。 https://docs.oracle.com/javase/8/docs/api/java/util/List.html#sort-java.util.Comparator- – 2017-12-09 16:38:47

7

阅读本Oracle Doc

它说:“当务之急是用户迭代它时,返回的列表上手动同步”

+1

这个文档听起来更像是一个教条而不是解释。这就是我找到这个讨论的原因。 – beemaster 2016-12-02 08:57:42

0

什么样的事情已被他人所提到的,同步的集合是thread-安全,但对这些集合的复合操作默认情况下不保证是线程安全的。

据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) - >除去
线程B(getLast)-------------------->得到

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