为什么List<T>不是线程安全的?

44

以下内容来自以下网站:

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

不幸的是,List<> 不是线程安全的(C# 的 ArrayList 和 Java 的 Vector 是线程安全的)。 C# 还有一个 Hashtable;它的泛型版本是:

是什么让 List<T> 不是线程安全的?这是 .NET 框架工程师实现上的问题吗?还是说泛型本身就不是线程安全的?


根据MSDN,ArrayListList(Of T)具有相同的线程安全性。但是,ArrayList提供了Synchronized包装器。 - Mark Hurd
6个回答

69

您需要对Java的Vector类型的线程安全性进行分类。 Java的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


14
小提示: “无锁”意味着“在没有任何锁的情况下同步”,而不是“没有锁”。http://en.wikipedia.org/wiki/Lock_free - Craig Gidney
像“Count”这样的方法可以在线程安全的集合上完全合理和合法地使用,以决定不再烦恼某些事情。例如,如果需要使用从队列中取出的两个项目执行某些操作,则完全合理的模式可能是使用属性来检查计数(使用读屏障而不是锁定),如果计数足够,则获取锁并再次检查计数。如果仍然足够,则执行操作;否则跳过它。无论如何都要释放锁定。 - supercat
@JaredPar:我刚举了一个例子:如果在获取锁之前检查Count()是否包含足够完成所需操作的值,那么如果没有要处理的内容,就可以避免锁定获取/释放周期。 如果集合通常不包含足够的数据以值得处理,则这种优化有时可以提供显着的好处。 偶尔会检查Count(),决定获取锁定,再次检查Count(),并发现数据已经消失,因此不再需要执行任何操作,但是... - supercat
如果发生这种情况,唯一的后果就是浪费获取锁的努力。如果在95%的情况下可以避免那些无事可做的浪费,即使不能在所有情况下都避免,也可能是一个胜利。 - supercat
@supercat,这仍然是一个需要锁定的解决方案的总体情况。这就是我在帖子中试图强调的重点。我同意在某些有限的情况下,像Count这样的调用可以用作廉价的锁避免技巧。但请注意,这在vector中实际上并不起作用,因为size本身会获取锁。 - JaredPar
显示剩余4条评论

21

为什么会是线程安全?并不是每个类都是线程安全的。事实上,默认情况下,大多数类都不是线程安全的。

线程安全意味着修改列表的任何操作都需要与同时访问进行交互锁定。即使只有一个线程将使用这些列表,这也是必要的。这样做将非常低效。


9

将类型实现为非线程安全的是一种设计决策。集合提供接口ICollection的属性SyncRoot以及某些集合的Synchronized()方法,用于显式同步数据类型。

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

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

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

4
请参阅JaredPar的文章,了解为什么这几乎肯定不是您想要做的内容解释。 - Daniel Earwicker

2

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


2
JaredPar提到的竞态条件可能性是依赖于Vector的所谓线程安全性所带来的可怕后果。这种情况会导致“每个第九个星期二应用程序做一些奇怪的事情”这样的缺陷报告,让你发疯。
有一些真正线程安全的集合 在 .Net 4 中推出, 它们有一个有趣的副作用,即允许在枚举时单线程 修改集合, 但是线程安全会带来 性能损失,有时还相当大。
因此,对于框架开发人员来说,逻辑上应该尽可能保持类的性能,以满足那些可能不会进行多线程操作的95%用户,并依靠那些需要进行多线程操作的用户知道如何保持其安全。

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接