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

2022-09-01 15:13:55

我应该使用旧的同步矢量集合,具有同步访问权限的ArrayList还是Collections.synchronizedList或其他一些解决方案进行并发访问?

我在“相关问题”和搜索中都看不到我的问题(“使您的集合线程安全?不一样”)。

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

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

无论如何,我对并发问题不是很熟悉,我查找了一些信息,并想知道在旧的(可能是过时的)Vector(它通过设计上具有同步操作),ArrayList与周围关键部分(添加/删除/行走操作)或使用Collections.syncedList返回的列表(甚至不知道如何使用后者)之间进行选择。synchronized (myList) { }

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

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


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

总结一下:

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

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


答案 1

Vector 和 Collections.synchronizedList() 返回的列表在道德上是一回事。我认为Vector被有效地(但实际上不是)弃用,并且总是更喜欢同步的列表。一个例外是需要Vector的旧API(特别是JDK中的API)。

使用裸 ArrayList 并独立同步使您能够更精确地调整同步(通过在互斥块中包含其他操作,或者在一个原子操作中将对 List 的多个调用放在一起)。缺点是可以编写在同步外部访问裸 ArrayList 的代码,该代码已损坏。

您可能要考虑的另一个选项是CopyOnWriteArrayList,它将为您提供线程安全性,就像在Vector和同步ArrayList中一样,还可以使用迭代器,因为它们正在处理数据的非实时快照,因此不会抛出ConcurrentModificationException。

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


答案 2

我强烈推荐《Java 并发实践》这本书。

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

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