确保您的收藏线程安全
本文关键字:线程 安全 收藏 确保 | 更新日期: 2023-09-27 17:47:24
在设计集合类时,是否有理由不私下实现锁定以确保线程安全?或者我应该把责任交给收藏品的消费者吗?
有什么理由不私下实现锁定以确保线程安全吗?
这取决于情况。您的目标是编写一个可由多个线程访问的集合类吗?如果是,请确保线程安全。如果没有,不要浪费时间。这种事情就是人们在谈论"过早优化"时所指的
解决你遇到的问题。不要试图解决你认为未来几年可能会遇到的问题,因为你看不到未来,你总是错的。
注意:您仍然需要以可维护的方式编写代码,这样,如果确实需要加入并向集合添加锁定,就不会太难了。我的观点是"不要实现你不需要也不会使用的功能"
对于Java,为了速度,你应该保持不同步。如果需要,集合的使用者可以包装在同步包装器中。
线程安全集合可能具有欺骗性。Jared Par发布了几篇关于线程安全集合的有趣文章:
问题是有几个线程安全集合的级别。我发现当大多数人说线程安全收集它们的真正含义"一个不会修改和访问时损坏来自多个线程"
但是如果构建数据线程安全列表这么简单,为什么微软不将这些标准集合添加到框架
答:ThreadSafeList是实际上不可用的类,因为设计会让你走向糟糕密码
这个设计的缺陷不是显而易见,直到你检查如何列出是常用的。例如,以以下代码尝试从列表中取出第一个元素如果有的话。
static int GetFirstOrDefault(ThreadSafeList<int> list) {
if (list.Count > 0) {
return list[0];
}
return 0; }
此代码是典型的比赛条件。考虑只有一个>元素。如果另一个线程在If语句和return语句之间删除了该元素,那么return语句将抛出异常,因为它试图访问列表中的无效索引。即使ThreadSafeList是数据线程安全的,也无法保证一个调用的返回值在对同一对象的下一次调用中的有效性
http://blogs.msdn.com/b/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx
http://blogs.msdn.com/b/jaredpar/archive/2009/02/16/a-more-usable-thread-safe-collection.aspx
集合类需要尽可能快。因此,不要锁。
调用代码将知道锁的最佳位置,而集合类则不知道。在最坏的情况下,应用程序将不得不添加一个额外的锁,这意味着会出现两个锁,使性能命中率翻倍。
我个人会把它留给消费者。它将使您的集合类更加通用。
只需在文档中明确您没有使其线程安全并将其排除在外,或者,如果对于您的应用程序,您希望其线程安全,则使其线程安全并在文档中为其注意这一点。唯一的规则是对其进行文档化。除此之外,为您创建您的类,如果其他人想使用它,他们可以。
如果我正在寻找一个集合类,并且我需要线程安全功能,而您的类没有这些功能,我将立即跳到下一个产品,看看它们提供了什么。你的收藏不会再引起我的注意了。
注意开头的"如果"。有些客户会想要,有些不会,有些则不在乎。如果你要为消费者打造一个工具包,那么为什么不同时提供这两个品种呢?这样我就可以选择使用哪一个,但如果我想要线程安全,你仍然会引起我的注意,我不必自己写。
使集合线程安全是扼杀Java的Vector和Hashtable类的原因。正如前面建议的那样,客户端将其封装在线程安全包装器中,或者同步方法子集上的数据访问,比每次访问类时都要容易得多。几乎没有人使用Vector或Hashtable,如果他们使用了,他们会被嘲笑,因为他们的替代品(ArrayList和HashMap)要快得多。这很不幸,因为我(来自C++背景)更喜欢"Vector"名称(STL),但ArrayList会一直存在。
不使其线程安全的主要原因是性能。线程安全代码可能比非安全代码慢100倍,所以如果您的客户端不想要该功能,那将是一个相当大的浪费。
请注意,如果您试图使任何类线程安全,则需要决定常见的使用场景。
例如,在集合的情况下,仅仅使所有属性和方法单独线程安全对消费者来说可能不够好,因为首先读取计数,然后循环,或者类似的操作,如果计数在读取后发生变化,就不会有多大好处。
基本上,将集合设计为线程安全,锁定在类的两个方法中实现:lock()和unlock()。在任何需要的地方给他们打电话,但让他们空着。然后将实现lock()和unlock()方法的集合子类化。两个等级的价格一个。
不确保集合线程安全的一个很好的理由是提高单线程性能。示例:向量上的ArrayList。将线程安全推迟到调用方,可以通过避免锁定来优化未同步的用例。
提高多线程性能是确保集合线程安全的一个很好的理由。示例:ConcurrentHashMap over HashMap。由于CHM内部化了多线程问题,因此与外部同步相比,它可以更有效地分条锁定以实现更大的并发访问。
这将使您无法从多个线程同时访问集合,即使您知道您触摸的元素没有被其他人使用。
一个示例是具有基于整数的索引访问器的集合。每个线程可能从其id中知道可以访问哪些索引值,而不必担心脏读/写。
另一种可能会导致不必要的性能损失的情况是,数据只从集合中读取,而不写入。
我同意由消费者决定是正确的方法。If为使用者提供了更大的灵活性,可以决定是在上同步Collection实例,还是在上同步不同的对象。例如,如果有两个列表都需要更新,那么使用单个锁将它们放在一个同步块中可能是有意义的。
如果您创建了一个集合类,请不要使其线程安全。很难做对(例如正确和快速),当你做错时(heisenbugs)给消费者带来的问题也很难调试。
相反,如果需要的话,实现其中一个CollectionAPI并使用Collections.synchronizedCollection(yourCollectionInstance)来获得线程安全的实现。
只需参考类javadoc中相应的Collections.synchronizedXXX方法;它将表明你在设计中考虑了线程安全性,并确保消费者有一个线程安全的选择。
这是一个良好的开端。
线程安全字典
但您会注意到,您失去了集合的一个伟大功能——枚举。您无法对枚举器进行线程安全,这实际上是不可行的,除非您实现自己的枚举器,该枚举器将实例锁定回集合本身。我怀疑这会造成严重的瓶颈和潜在的僵局。
从JDK 5开始,如果您需要一个线程安全的集合,我首先会看看java.util.concurrent中已经实现的集合之一是否可以工作。正如Java Concurrency In Practice的作者所指出的(包括编写大多数类的人),正确实现这些是非常困难的,尤其是在性能很重要的情况下。
报价http://download.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html
并发收款
除了Queues,此软件包还提供设计的集合实现用于多线程上下文:ConcurrentHashMap,ConcurrentSkipListMap,ConcurrentSkipListSet,CopyOnWriteArrayList,以及CopyOnWriteArraySet。当许多线程应访问给定的集合,ConcurrentHashMap是通常比同步HashMap和ConcurrentSkipListMap通常比同步树映射。A.CopyOnWriteArrayList优于当预期的读取次数和穿越次数大大超过列表的更新数。