控制对 Java 集合的并发访问的最佳方法
Posted
技术标签:
【中文标题】控制对 Java 集合的并发访问的最佳方法【英文标题】:Best way to control concurrent access to Java collections 【发布时间】:2010-10-08 09:08:50 【问题描述】:我应该使用旧的同步向量集合、具有同步访问的 ArrayList 还是 Collections.synchronizedList 或其他一些用于并发访问的解决方案?
我在相关问题和搜索中都没有看到我的问题(Make your collections thread-safe? 不一样)。
最近,我不得不对我们应用程序的 GUI 部分进行某种单元测试(主要是使用 API 来创建框架、添加对象等)。 由于这些操作的调用速度比用户调用的快得多,因此在尝试访问尚未创建或已删除的资源的方法方面存在许多问题。
在 EDT 中发生的一个特定问题来自于遍历视图的链接列表,同时在另一个线程中更改它(在其他问题中遇到 ConcurrentModificationException)。 不要问我为什么它是一个链表而不是一个简单的数组列表(甚至更少,因为我们通常在里面有 0 或 1 个视图......),所以我在我的问题中采用了更常见的 ArrayList(因为它有一个表弟)。
无论如何,我对并发问题不太熟悉,我查了一些信息,想知道在旧的(并且可能已经过时的)Vector(它在设计上具有同步操作)和带有synchronized (myList)
的 ArrayList 之间选择什么关键部分(添加/删除/遍历操作)或使用 Collections.synchronizedList 返回的列表(甚至不确定如何使用后者)。
我最终选择了第二个选项,因为另一个设计错误是暴露对象(getViewList() 方法...)而不是提供使用它的机制。
但是其他方法的优缺点是什么?
[编辑] 这里有很多好的建议,很难选择一个。我会选择更详细并提供链接/思想的食物... :-) 我也喜欢 Darron 的。
总结一下:
正如我所怀疑的那样,Vector(以及它的邪恶孪生兄弟,可能还有 Hashtable)在很大程度上已经过时了,我看到人们说它的旧设计不如新集合,甚至在强制同步的缓慢性之外单线程环境。如果我们保留它,主要是因为旧的库(和部分 Java API)仍在使用它。 与我所想的不同,Collections.synchronizedXxxx 并不比 Vector 更现代(它们似乎与 Collections 是同时代的,即 Java 1.2!),实际上并没有更好。很高兴知道。简而言之,我也应该避免使用它们。 毕竟手动同步似乎是一个很好的解决方案。可能存在性能问题,但在我的情况下,这并不重要:对用户操作执行的操作、少量集合、不经常使用。 java.util.concurrent 包值得牢记,尤其是 CopyOnWrite 方法。我希望我做对了... :-)
【问题讨论】:
另见***.com/questions/510632/… 【参考方案1】:Vector 和 Collections.synchronizedList() 返回的 List 在道德上是一回事。我认为 Vector 被有效地(但实际上不是)弃用,并且总是更喜欢同步列表。一个例外是需要 Vector 的旧 API(尤其是 JDK 中的 API)。
使用裸 ArrayList 并独立同步使您有机会更精确地调整同步(通过在互斥块中包含其他操作或将多个对 List 的调用放在一个原子操作中)。不利的一面是,可以编写在同步之外访问裸 ArrayList 的代码,这是被破坏的。
您可能要考虑的另一个选项是 CopyOnWriteArrayList,它可以像 Vector 和同步 ArrayList 一样为您提供线程安全,但也可以提供不会抛出 ConcurrentModificationException 的迭代器,因为它们正在处理数据的非实时快照。
您可能会发现最近关于这些主题的一些博客很有趣:
Java Concurrency Bugs #3 - atomic + atomic != atomic Java Concurrency Bugs #4: ConcurrentModificationException CopyOnWriteArrayList concurrency fun【讨论】:
【参考方案2】:我强烈推荐《Java Concurrency in Practice》这本书。
每个选择都有优点/缺点:
-
向量 - 被认为是“过时的”。与更主流的合集相比,它获得的关注和错误修复可能更少。
您自己的同步块 - 很容易出错。通常提供比以下选择更差的性能。
Collections.synchronizedList() - 由专家完成的选择 2。这仍然不完整,因为需要原子的多步操作(get/modify/set 或迭代)。
来自 java.util.concurrent 的新类 - 通常具有比选择 3 更有效的算法。关于多步骤操作的类似警告适用,但通常会提供帮助您的工具。
【讨论】:
【参考方案3】:我想不出比ArrayList
更喜欢Vector
的充分理由。 Vector
上的列表操作是同步的,这意味着多个线程可以安全地更改它。就像你说的,ArrayList 的操作可以使用Collections.synchronizedList
同步。
请记住,即使使用同步列表,如果在另一个线程修改集合时迭代集合,您仍然会遇到ConcurrentModificationExceptions
。因此,在外部协调访问非常重要(您的第二种选择)。
用于避免迭代问题的一种常用技术是迭代集合的不可变副本。见Collections.unmodifiableList
【讨论】:
【参考方案4】:我总是先去 java.util.concurrent (http://java.sun.com/javase/6/docs/api/java/util/concurrent/package-summary.html) 包,看看有没有合适的集合。 java.util.concurrent.CopyOnWriteArrayList 类如果您对列表进行少量更改但需要更多迭代,则该类很好。
我也不相信 Vector 和 Collections.synchronizedList 会阻止 ConcurrentModificationException。
如果您没有找到合适的集合,那么您必须自己进行同步,如果您不想在迭代时持有锁,您可能需要考虑制作一个副本并迭代该副本。
【讨论】:
【参考方案5】:我不相信 Vector 返回的 Iterator
以任何方式同步 - 这意味着 Vector
不能保证(就它自己)一个线程没有修改底层集合,而另一个线程正在迭代它。
我相信要确保迭代是线程安全的,您必须自己处理同步。假设同一个 Vector/List/object 被多个线程共享(从而导致您的问题),您不能只在该对象本身上进行同步吗?
【讨论】:
【参考方案6】:最安全的解决方案是完全避免并发访问共享数据。不要让非 EDT 线程对相同的数据进行操作,而是让它们调用 SwingUtilities.invokeLater()
并使用 Runnable 执行修改。
说真的,共享数据并发是毒蛇的巢穴,您永远不会知道某个地方是否隐藏着另一个竞争条件或死锁,等待时机在最坏的情况下咬您一口。
【讨论】:
【参考方案7】:CopyOnWriteArrayList 值得一看。它专为通常从中读取的列表而设计。每次写入都会导致它在幕后创建一个新数组,因此那些遍历数组的人不会得到 ConcurrentModificationException
【讨论】:
以上是关于控制对 Java 集合的并发访问的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章