【问题标题】:Java Collection Framework: Some thread safe and some not?Java Collection Framework:有些线程安全,有些不是?
【发布时间】:2017-12-17 12:51:34
【问题描述】:

我正在学习 Java 集合框架(不是 Concurrent Collection 框架),我了解到有些 Collection 实现是线程安全的,有些则不是。

在我阅读的大部分材料中,都提到xyz 是线程安全的,abc 不是线程安全的。

但是,决定是否保持给定集合类型(例如,List、Set、Queue,甚至在 Map.. 中)线程安全的逻辑是什么?

我的问题是关于“传统”收集框架而不是并发收集框架。

任何有助于理解这一点的意见都会有很大帮助。

【问题讨论】:

  • 当你在问题中明确排除ConcurrentHashMap时,你为什么要标记concurrenthashmap
  • @Andreas:请建议包含哪些标签?我会这样做的。
  • @GuyKhmel:它没有说明一些保持同步而一些不保持同步的逻辑。
  • 逻辑是在集合框架之前的“遗留”类是同步的。 “传统”类不是线程安全的,而较新的“并发”类都是线程安全的(因此得名)。

标签: java collections concurrenthashmap


【解决方案1】:

线程安全带来了开销(尽管在现代 VM 中,开销远低于设计集合框架时的开销)。因此,除非特别需要,否则集合不是线程安全的,JDK1.1 集合除外——在设计它们时,其理念更像是“让我们以牺牲一些性能为代价,尽可能少地出错”。

我们在 Java API 发展过程中有几个阶段。

JDK1.1

在 Java 1.1 版本中,我们有数据结构 VectorHashtable。它们完全同步,提供一定程度的线程安全。

JDK1.2

在 Java 1.2 版本中,引入了集合框架。基本集合都不是线程安全的(它们不同步任何操作):ArrayListLinkedListHashMapTreeMapSet 实现。

但可以通过调用Collections.synchronizedMapCollections.synchronizedList等方式获取同步版本。

JDK1.5

在 Java 1.5 版本中,引入了java.util.concurrent 框架。它们包含专门为多线程使用而构建的数据。这些提供了一定程度的线程安全。


请注意,即使使用同步集合,也可能会引入数据竞争;这只意味着你不能破坏集合的内部结构(将保持集合的所有不变量)

例如,如果您有一个两步过程,首先检查集合是否不包含某些元素,然后在第二步中插入该元素。如果您没有为这两个步骤提供自己的同步,则如果两个线程同时执行此操作,则可以将元素添加两次。

【讨论】:

  • 感谢您的回答。那么,对于他们使其线程安全的场景,令人信服的原因是什么?你能详细说明一下吗?有什么权威的参考资料可以参考吗?
  • @CuriousMind 挖掘当时的文章/博客文章并不容易。集合框架的大部分文档都将 JDK1.2 的内容与 JDK1.5 的内容混合在一起。我看看能不能找到。
  • @CuriousMind 您是在问为什么要同步旧版 (JDK1.1) 类,还是要问他们为什么要创建并发 (JDK1.5) 类?正如答案所说,传统的(JDK1.2)类不是线程安全的,但可以通过使用Collections.synchronizedXxx 包装它们来实现。添加了并发类,因为他们了解到当存在高争用时同步对性能不利,因此创建了替代的非同步线程安全类,每个类都有自己的优缺点。
  • @CuriousMind 嗯,这个来自 Josh Blog 的 2016 年演示文稿进入了历史,但它没有具体说明集合不同步的原因,但它确实提到了“提高程序速度”的设计目标,这主要是因为集合不再同步,而且当时没有像我们现在这样的无竞争同步的快速路径。 cs.cmu.edu/~charlie/courses/15-214/2016-fall/slides/…
  • @CuriousMind 但答案特定于遗留类:Legacy = synchronized。传统 = 不是线程安全的。他们无法更改遗留类,因此没有为那些做出决定。这意味着您的问题实际上是:“为什么他们决定不使 Collection Framework 类同步(不包括并发)?”答案:性能。正如他们从遗留类中学到的那样,使类线程安全的成本性能。
【解决方案2】:

正如其他人所说,并发集合具有运行时和潜在的内存开销,因此线程安全集合和不安全集合是分开的。

您可以在单线程库中找到的大多数数据结构都有几个线程安全的替代方案。一个值得注意的例外是List,这可能是因为应用程序中很少需要并发列表。

对于队列和堆栈之类的东西,您有大量的选择,因为让一个生产者和一个或多个消费者同时拉入和推入队列是很常见的事情。要实现缓存,您可能会依赖映射,这就是为什么并发映射也得到很好支持的原因。

事实上,一些数据结构并没有真正反映在线程安全 API 中,这仅仅是因为它们通常在多线程上下文中没有用处。

【讨论】:

    【解决方案3】:

    原因很可能与性能有关。多线程之间的同步是一项代价高昂的操作,尤其是在有大量元素的情况下。

    【讨论】:

      猜你喜欢
      • 2011-08-28
      • 1970-01-01
      • 2014-06-05
      • 2015-07-20
      • 1970-01-01
      • 2012-03-10
      • 1970-01-01
      • 1970-01-01
      • 2011-01-30
      相关资源
      最近更新 更多