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

Best way to control concurrent access to Java collections

我应该使用旧的同步向量集合、带同步访问的ArrayList或Collections.SynchronizedList或其他一些并发访问解决方案吗?

我在相关问题或搜索中都看不到我的问题(确保您的收藏安全吗?不一样)。

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

在EDT中发生的一个特定问题来自于在另一个线程中修改视图的链接列表(在其他问题中获得一个ConcurrentModificationException)。不要问我为什么它是一个链接列表而不是一个简单的数组列表(甚至比我们在……中的0或1视图中的更少),所以我在我的问题中选择了更常见的数组列表(因为它有一个老表亲)。

总之,我对并发性问题不太熟悉,我查找了一些信息,想知道在旧的(可能是过时的)向量(按设计实现了同步操作)和围绕关键部分(添加/删除/遍历操作)使用synchronized (myList) { }的arraylist,或使用collections.synchronizedList返回的列表(不是e)之间选择什么。当然知道如何使用后者)。

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

但是其他方法的利弊是什么呢?

[编辑]这里有很多好建议,很难选择。我会选择更详细的链接/食物供思考…我也喜欢达伦的。

总结:

  • 正如我所怀疑的,vector(及其邪恶的孪生体,hashtable,也可能)在很大程度上已经过时了,我见过有人告诉我,它的旧设计不如新的集合好,甚至在单线程环境中,同步的速度也很慢。如果我们保持它,这主要是因为旧的库(和Java API的一部分)仍然使用它。
  • 不像我想象的那样,Copyth.SimulixXxxx并不比向量更现代(它们对于集合来说是当代的,例如,Java 1.2!)事实上,也不是更好。很高兴知道。总之,我也应该避开它们。
  • 毕竟,手动同步似乎是一个很好的解决方案。可能存在性能问题,但在我的例子中,这并不重要:对用户操作执行的操作、小集合、不经常使用。
  • concurrent包值得记住,尤其是copyonwrite方法。

我希望我做对了…-)


vector和collections.synchronizedList()返回的列表在道义上是相同的。我认为vector是有效的(但不是实际的)被否决的,并且总是倾向于使用同步列表。唯一的例外是需要向量的旧API(尤其是JDK中的API)。

使用一个裸数组列表并独立地进行同步,可以让您有机会更精确地调整同步(要么在互斥块中包含其他操作,要么在一个原子操作中组合对列表的多个调用)。其缺点是,可以在同步之外编写访问裸数组列表的代码,而这会被破坏。

您可能要考虑的另一个选项是CopyOnWriteArrayList,它将为您提供与向量和同步ArrayList中一样的线程安全性,但也提供迭代器,这些迭代器在处理数据的非活动快照时不会引发ConcurrentModificationException。

你可能会发现最近关于这些主题的一些博客很有趣:

  • Java并发错误3原子-原子!=原子
  • Java并发错误(4):并发修改异常
  • CopyOnWriteArrayList并发乐趣

我强烈推荐这本书"实践中的Java并发"。

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

  • 矢量-被认为是"过时的"。它可能比主流收藏受到较少的关注和错误修复。
  • 你自己的同步块-很容易出错。通常比下面的选择表现差。
  • collections.synchronizedList()-选项2由专家完成。这仍然不完整,因为多步骤操作需要原子化(get/modify/set或迭代)。
  • 来自java.util.concurrent的新类-通常具有比选项3更高效的算法。关于多步骤操作的类似警告也适用,但通常会提供帮助您的工具。

  • 我想不出一个很好的理由来比ArrayList更喜欢VectorVector上的列表操作是同步的,这意味着多个线程可以安全地更改它。如您所说,arraylist的操作可以使用Collections.synchronizedList进行同步。

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

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


    我总是先访问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。

    如果找不到合适的集合,则必须进行自己的同步;如果在迭代时不想持有锁,则可以考虑制作副本并迭代副本。


    我不认为矢量返回的Iterator以任何方式是同步的,这意味着Vector不能(单独)保证一个线程不会修改基础集合,而另一个线程正在迭代它。

    我相信为了确保迭代是线程安全的,您必须自己处理同步。假设同一个向量/列表/对象正由多个线程共享(从而导致您的问题),您就不能在该对象本身上同步吗?


    CopyOnWriteArrayList值得一看。它是为通常从中读取的列表而设计的。每次写操作都会导致它在封面后面创建一个新的数组,这样在数组中迭代的那些操作就不会得到ConcurrentModificationException。


    最安全的解决方案是避免同时访问共享数据。不要让非EDT线程在同一个数据上操作,而是让它们使用执行修改的可运行文件调用SwingUtilities.invokeLater()

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