【问题标题】:Why is List<T> not thread-safe?为什么 List<T> 不是线程安全的?
【发布时间】:2016-11-20 11:25:46
【问题描述】:

来自以下网站:

http://crfdesign.net/programming/top-10-differences-between-java-and-c

不幸的是,List&lt;&gt; 不是线程安全的(C# 的 ArrayList 和 Java 的 Vector 是线程安全的)。 C#也有一个Hashtable;通用版本是:

是什么让List&lt;T&gt; 不是线程安全的?是.NET框架工程师的实现问题吗?还是泛型不是线程安全的?

【问题讨论】:

标签: .net generics thread-safety


【解决方案1】:

您确实需要对 Java 的 Vector 的线程安全类型进行分类。 Javas Vector 可以安全地从多个线程中使用,因为它在方法上使用同步。状态不会被破坏。

然而,Java 的向量的用处受限于没有额外同步的多线程。例如,考虑从向量中读取元素的简单行为

Vector vector = getVector();
if ( vector.size() > 0 ) { 
  object first = vector.get(0);
}

这种方法不会破坏向量的状态,但也是不正确的。没有什么可以阻止另一个线程在 if 语句和 get() 调用之间改变向量。由于竞争条件,此代码可能并且最终失败。

这种类型的同步只在少数情况下有用,而且肯定不便宜。即使您不使用多个线程,您也需要为同步付出可观的代价。

.Net 选择默认不支付这个价格,因为它的用处有限。相反,它选择实现一个无锁列表。作者负责添加任何同步。它更接近 C++ 的“只为你使用的东西付费”的模型

我最近写了几篇关于使用仅具有内部同步的集合(如 Java 的向量)的危险的文章。

引用向量线程安全:http://www.ibm.com/developerworks/java/library/j-jtp09263.html

【讨论】:

  • 小注:“无锁”意味着“同步无锁”,而不是“无锁”。 en.wikipedia.org/wiki/Lock_free
  • 我同意 Strilanc:我不得不重复使用“无锁”这个术语。
  • 诸如“Count”之类的方法可以完全合理且合法地用于线程安全集合,以决定不打扰某事。例如,如果需要使用从队列中取出的两个项目执行某些操作,那么一个完全合理的模式可能是使用属性来检查计数(使用读屏障而不是锁),并且如果计数足够,获取锁并再次检查计数。如果仍然足够,则执行操作;否则跳过它。无论如何都要释放锁。
  • @supercat 您的解决方案虽然涉及锁定,但因此是正确的。我的论点是,在 java 库中编码的Vector 不能以您描述的方式安全使用而没有锁。这是不可能的,因为您找到的每个结果在您阅读的那一刻都是无效的。
  • @JaredPar:我的意思是,像Count 这样的方法通常在锁之外很有用,用于早期退出的常见情况。
【解决方案2】:

为什么它是线程安全的?不是每个班级都是。事实上,默认情况下,类不是线程安全的。

线程安全意味着任何修改列表的操作都需要互锁以防止同时访问。即使对于那些只会被单个线程使用的列表,这也是必要的。那将是非常低效的。

【讨论】:

    【解决方案3】:

    实现非线程安全的类型只是一个设计决定。集合提供接口ICollection 的属性SyncRoot 和一些集合上的Synchronized() 方法,用于显式同步数据类型。

    使用SyncRoot 在多线程环境中锁定对象。

    lock (collection.SyncRoot)
    {
       DoSomething(collection);
    }
    

    使用collection.Synchronized() 获取集合的线程安全包装器。

    【讨论】:

    • 查看 JaredPar 的文章,了解为什么这几乎肯定不是您想要做的。
    【解决方案4】:

    JaredPar 提到的竞争条件可能性是依赖于 Vector 假定的线程安全的可怕后果。正是这种情况会导致“每个星期二的应用程序都会做一些奇怪的事情”——这种缺陷报告会让你发疯。

    有许多真正线程安全的集合coming in .Net 4,有一个有趣的副作用是它们允许单线程modification of the collection while enumerating,但是有一个performance hit 带有线程安全性,有时相当大一。

    因此,对于框架开发人员来说,合乎逻辑的做法是让 95% 的用户尽可能保持类的性能,这些用户可能不会进行线程处理,而依赖于使用多线程的用户来了解他们必须做什么确保其安全。

    【讨论】:

      【解决方案5】:

      为了真正的线程安全,List&lt;&gt; 和其他集合类型需要是不可变的。随着 .NET 4.0 中出现的 .NET 并行扩展,我们将看到最常用集合的线程安全版本。 Jon Skeet 涉及其中的一些内容。

      【讨论】:

        【解决方案6】:

        使用 SynchronizedCollection 它还提供了一个构造函数参数来使用共享同步:)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-26
          • 2011-08-17
          • 2014-08-04
          • 2012-11-20
          • 2013-10-11
          • 2020-10-10
          相关资源
          最近更新 更多