控制Java集合的并发访问的最佳方法

时间:2009-02-18 15:53:08

标签: java collections concurrency

我应该使用旧的同步Vector集合,带有同步访问的ArrayList还是Collections.synchronizedList或其他一些并发访问的解决方案?

我在相关问题或我的搜索中都没有看到我的问题(Make your collections thread-safe?不一样)。

最近,我不得不对应用程序的GUI部分进行单元测试(基本上使用API​​来创建框架,添加对象等)。 由于这些操作的调用速度比用户快得多,因此它显示了尝试访问尚未创建或已删除的资源的方法的许多问题。

在EDT中发生的一个特殊问题来自于在另一个线程中更改它时在一个链接的视图列表中行走(在其他问题中获得ConcurrentModificationException)。 不要问我为什么它是一个链表而不是一个简单的数组列表(更少,因为我们通常在0或1视图里面...),所以我在我的问题中采用了更常见的ArrayList(因为它有一个老表弟。

无论如何,我不太熟悉并发问题,我查了一些信息,想知道在旧的(可能是过时的)Vector(已经按设计同步操作),ArrayList和{{1}之间做出选择。围绕关键部分(添加/删除/遍历操作)或使用Collections.synchronizedList返回的列表(甚至不确定如何使用后者)。

我最终选择了第二个选项,因为另一个设计错误是暴露对象(getViewList()方法...)而不是提供使用它的机制。

但其他方法的优点和缺点是什么?


[编辑]这里有很多好的建议,很难选择一个。我会选择更详细的,提供思路的链接/食物...... :-)我也喜欢Darron的。

总结:

  • 正如我所怀疑的那样,Vector(及其邪恶的双胞胎,也可能是Hashtable)在很大程度上已经过时了,我看到有人说它的旧设计不如新系列那么好,除了同步的缓慢,甚至在单线程环境。如果我们保留它,主要是因为较旧的库(以及Java API的一部分)仍然使用它。
  • 与我的想法不同,Collections.synchronizedXxxx并不比Vector更现代(它们似乎是Collections的当代,即Java 1.2!)而实际上并不是更好。很高兴知道。简而言之,我也应该避免它们。
  • 毕竟手动同步似乎是一个很好的解决方案。可能存在性能问题,但在我的情况下并不重要:用户操作,小型收集,不经常使用的操作。
  • java.util.concurrent包值得记住,尤其是CopyOnWrite方法。

我希望我做对了......: - )

7 个答案:

答案 0 :(得分:23)

Vector和Collections.synchronizedList()返回的List在道德上是一回事。我会认为Vector有效(但实际上)不推荐使用,而且总是更喜欢同步List。唯一的例外是旧API(特别是JDK中的API)需要Vector。

使用裸ArrayList并独立同步,您可以更精确地调整同步(通过在互斥块中包含其他操作,或者在一个原子操作中将多个调用放在一起)。不利的一面是,可以编写访问同步之外的裸ArrayList的代码,该代码被破坏。

您可能需要考虑的另一个选项是CopyOnWriteArrayList,它将为您提供线程安全,如Vector和synchronized ArrayList,以及不会抛出ConcurrentModificationException的迭代器,因为它们正在处理数据的非实时快照。

您可能会发现有关这些主题的一些最新博客很有趣:

答案 1 :(得分:13)

我强烈推荐“Java Concurrency in Practice”一书。

每个选择都有优点/缺点:

  1. 矢量 - 被认为是“过时的”。与更多主流馆藏相比,它可能得到的关注和错误修复程度较低。
  2. 您自己的同步块 - 很容易出错。通常表现得比下面的选择更差。
  3. Collections.synchronizedList() - 由专家完成的选择2。这仍然是不完整的,因为需要原子的多步操作(获取/修改/设置或迭代)。
  4. 来自java.util.concurrent的新类 - 通常比选择3有更高效的算法。有关多步操作的类似注意事项适用,但通常会提供帮助您的工具。

答案 2 :(得分:10)

我想不出一个好Vector优于ArrayList的理由。 Vector上的列表操作是同步的,这意味着多个线程可以安全地更改它。就像你说的,可以使用Collections.synchronizedList来同步ArrayList的操作。

请记住,即使使用同步列表,如果在另一个线程修改集合时迭代集合,您仍会遇到ConcurrentModificationExceptions。因此,在外部协调该访问非常重要(您的第二个选项)。

用于避免迭代问题的一种常用技术是迭代集合的不可变副本。见Collections.unmodifiableList

答案 3 :(得分:7)

我将首先转到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。

如果找不到合适的集合,则必须进行自己的同步,如果在迭代时不想保持锁定,则可能需要考虑复制并迭代副本。

答案 4 :(得分:3)

我不相信Vector返回的Iterator以任何方式同步 - 这意味着Vector无法保证(在它自己)一个线程没有修改底层收集,而另一个线程正在迭代它。

我相信为确保迭代是线程安全的,您必须自己处理同步。假设多个线程正在共享相同的Vector / List /对象(从而导致您的问题),您是否只能同步该对象本身?

答案 5 :(得分:2)

最安全的解决方案是避免并发访问共享数据。不要让非EDT线程对相同的数据进行操作,而是让他们使用执行修改的Runnable调用SwingUtilities.invokeLater()

说真的,共享数据并发是一个毒蛇的巢,在那里你永远不会知道是否没有其他竞争条件或死锁隐藏在某个地方,在最糟糕的情况下咬你的时间咬你的屁股。

答案 6 :(得分:2)

CopyOnWriteArrayList值得一看。它专为通常读取的列表而设计。每次写入都会导致它在封面后面创建一个新数组,因此在数组中迭代不会得到ConcurrentModificationException