【问题标题】:Can I use the original list after it has been wrapped by Collections.synchronizedList(...)?我可以在 Collections.synchronizedList(...) 包装后使用原始列表吗?
【发布时间】:2017-10-06 14:12:58
【问题描述】:

From Javadocs for Collections class:

 public static <T> List<T> synchronizedList(List<T> list)

返回由指定支持的同步(线程安全)列表 列表。 为了保证串行访问,所有 对“后备名单”的访问是通过返回的 列表。

我是否理解正确,“支持列表”是指方法参数(列表),所以在代码行下面List&lt;String&gt; mySyncList = Collections.synchronizedList(origList); 我永远不会做类似 origList.anyMethodCall() 的事情,包括:origList.add("blabla") 或 @987654330 @, origList.set(1, "blabla")?虽然它编译!我可以通过不进行结构修改(origList.contains("blabla"))的方式访问“支持列表”吗?我想,我可以,但它也是“获得后盾”!并且应该遵循 Oracle 官方文档...

对吗,只有在我从 mySyncList 获得迭代器之后,在我完成使用这个迭代器之前,origList 结构修改时才会出现这个问题?

如果是这样,我说得对吗,如果线程 3 在结构上修改了 origList,那么在任何其他线程中迭代 mySyncList 都会产生 ConcurrentModificationException,但只要线程 3 以非结构方式修改 origList,绝对没有问题(contains()),或者thread-3在结构上修改了origList但mySyncList中没有迭代(mySyncList.add,mySyncList.remove,...)?

public static void main(String[] args) {
        List<String> origList = new ArrayList<>();
        origList.add("one");
        origList.add("two");
        origList.add("three");

        List<String> mySyncList = Collections.synchronizedList(origList);
        origList.add("blabla"); // prohibited by Oracle ??? :)))
        origList.add("blabla"); // prohibited by Oracle ??? :)))
        origList.add("blabla"); // prohibited by Oracle ??? :)))
        origList.add("blabla"); // prohibited by Oracle ??? :)))
        // now use mySyncList
        System.out.println(mySyncList); // no problem so far

        // P.S.: Maybe problem arises ONLY when origList STRUCTURALLY MODIFIED
        // AFTER I obtained iterator from mySyncList and BEFORE I finished
        // using this iterator? If so, such wording would be much preferable in
        // official docs!
    }

附:我的问题不同,我明白:

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

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

所以研究没有产生答案,我认真审查了以下所有内容:

感谢格雷接受下面的答案,我终于得出以下结论(我对格雷的回答的简要概述):

写完“List mySyncList = Collections.synchronizedList(origList)”后建议写在下一行“origList = null”。在实践中,大师从不在代码中的任何地方使用 origList (在任何线程中,在整个 prog 中)。

理论上以后使用 origList 是没有问题的,只要不进行结构修改(添加,删除不调用),但实际上没有人可以安全地保证仅非结构访问。 p>

这背后的思想是:您将 origList 转换为线程安全的 mySyncList,现在只将 mySyncList 用于多线程目的而忘记 origList !!!

【问题讨论】:

  • 确实抓住了这一点。你在问什么?
  • 我可以在不进行结构修改的情况下访问“支持列表”吗?只有在安全发布的情况下。

标签: java multithreading list collections synchronized


【解决方案1】:

[调用 synchronizedList(...)] 我永远不会做类似 origList.anyMethodCall() 的事情,包括:origList.add("blabla"), origList.remove("blabla"), origList.set( 1,“废话”)?

没错。每当您有多个线程更新列表时,您必须确保 所有 线程以同步方式处理它。如果一个线程通过Collections.synchronizedList(...) 包装器访问此列表,而另一个线程没有,那么您很容易遇到导致无限循环或随机运行时异常的数据损坏问题。

一般来说,一旦您拥有列表的同步包装版本,我会将origList 设置为null。不再使用它没有任何意义。我已经编写并查看了大量线程代码,并且从未看到有人在包装后使用原始列表。这真的感觉像是过早的优化和继续使用原始列表的重大黑客攻击。如果您真的担心性能,那么我会改用ConcurrentLinkedQueueConcurrentSkipList

我能否以不进行结构修改的方式访问“支持列表”(origList.contains("blabla"))?

是的,但是 没有线程可以进行结构修改。如果一个使用同步版本的线程添加了一个条目,然后另一个线程访问了非同步版本,那么相同的竞争条件可能导致列表的部分同步版本导致问题。

对吗,只有在我从 mySyncList 获得迭代器之后,在我完成使用这个迭代器之前,origList 结构修改时才会出现这个问题?

是的,只要您能保证这一点,那应该没问题。但是再一次,这感觉就像一个黑客。如果有人更改了另一个线程的行为,或者将来更改了时间,您的代码将在没有任何警告的情况下开始中断。

对于其他人来说,与 ConcurrentSkipList 这样的完全并发集合相反,同步列表包装器的问题在于迭代器是多个操作。引用javadocs for Collections.synchronizedList(...)

用户在迭代返回的列表时必须手动同步它。 [删除示例代码] 不遵循此建议可能会导致不确定的行为。

同步包装器在每次方法调用期间保护底层列表,但如果您使用迭代器遍历您的列表,同时另一个线程正在修改它,因为进行了多个方法调用,所有赌注都将被取消。请参阅sample code in the javadocs for more info

只要线程 3 非结构性地修改 origList(contains()),或者线程 3 结构性地修改 origList 但在 mySyncList 中没有迭代,绝对没有问题

只要两个线程都使用同步包装器,那么是的,应该没有问题。

origList.add("blabla"); // prohibited by Oracle ??? :)))

我们不是在谈论“禁止”,这听起来像是违反了语言定义。我们正在讨论与正确同步的集合一起使用的正确可重入代码。使用可重入代码,魔鬼在细节中。

【讨论】:

    猜你喜欢
    • 2013-05-21
    • 2021-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-14
    • 2016-05-27
    • 2013-06-27
    • 1970-01-01
    相关资源
    最近更新 更多