【问题标题】:Collections.synchronizedList and synchronizedCollections.synchronizedList 和同步
【发布时间】:2012-03-17 02:42:42
【问题描述】:
List<String> list = Collections.synchronizedList(new ArrayList<String>());
synchronized (list) {
    list.add("message");
}

这里真的需要块“同步(列表){}”吗?

【问题讨论】:

    标签: java collections synchronization


    【解决方案1】:

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

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

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

    【讨论】:

    • 上述声明的链接:docs
    • 像这样的同步集合的常见用例是在多个线程中添加到列表中,但仅在所有任务完成后才对其进行迭代。在这种情况下,我看不出有任何理由围绕迭代进行同步,因为它是从单个线程完成的。
    • 只要知道在迭代时列表没有更新,就不需要同步它。不过,我不知道我是否会将该用例描述为“常见”。 (我见过 ConcurrentModificationException 很多次。)在你提到的用例中,是什么阻止了一个线程在另一个线程迭代时再次添加到列表中?以及迭代线程如何“知道”其他线程何时完成更新列表?
    • Collections.synchornizedMap 类似,需要使用 synchronized 关键字进行 Map 的迭代或操作
    • 如果列表不同步的话,反正不会出现并发修改异常吗?
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

      Collections.synchronizedList add 方法的底层代码是:

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

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

      【讨论】:

      • 它使用的互斥锁的例子是什么?
      • 互斥锁是 javadoc 中记录的集合本身(this)。
      【解决方案4】:

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

      【讨论】:

      【解决方案5】:

      阅读此Oracle Doc

      上面写着“用户在迭代返回的列表时必须手动同步它”

      【讨论】:

      • 这个文档听起来更像是教条而不是解释。这就是我发现这个讨论的原因。
      【解决方案6】:

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

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

      • 迭代
      • 导航
      • 如果不存在则放置
      • 检查然后行动

      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方法,会导致并发问题。

      【讨论】:

        猜你喜欢
        • 2013-08-02
        • 1970-01-01
        • 1970-01-01
        • 2011-02-22
        • 2014-01-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多