我想根据API文档澄清一些关于ConcurrentHashMap vs ConcurrentSkipListMap的事情。
根据我的理解,ConcurrentHashMap 保证了多个线程插入的线程安全性。因此,如果您的地图仅由多个线程同时填充,则没有问题。然而,该 API 继续建议它不会保证锁定以进行检索,因此您可能会在这里获得误导性结果?
相比之下,对于 ConcurrentSkipListMap,它声明:"插入、删除、更新和访问操作由多个线程安全地并发执行"。所以我假设这没有哈希映射具有的上述检索问题,但显然这通常会带来性能成本?
在实践中,是否有人发现由于这种特定行为而需要使用ConcurrentSkipListMap,或者检索可能提供过时的视图通常无关紧要?
ConcurrentHashMap
检索反映最近完成的更新的结果 手术在发病时保持。对于聚合操作,例如 放全部和清晰,并发检索可能反映插入或 仅删除部分条目。
它使用易失性语义进行get(key)
。如果 Thread1 调用 put(key1, value1)
并且紧接着 Thread2 调用get(key1)
,Thread2 不会等待 Thread1 完成其put
,它们不会彼此同步,并且 Thread2 可以获得旧的关联值。但是,如果在线程 2 尝试get(key1)
之前put(key1, value1)
在线程 1 中完成,则保证线程 2 会获得此更新 ( value1
)。
ConcurrentSkipListMap 被排序并提供
包含键、获取、 的预期平均日志 (n) 时间成本 放置和删除操作及其变体
ConcurrentSkipListMap
不是那么快,但在需要排序线程安全映射时很有用。
然而,该 API 继续建议它不会保证锁定以进行检索,因此您可能会在这里获得误导性结果?
有趣的是,ConcurrentSkipListMap也没有,事实上CSLM是完全非阻塞的。
在 Java 7 中,出于所有意图和目的,CHM 在执行读取时都是非阻塞的。事实上,Java 8 更新的 CHM 实现具有完全非阻塞读取。
这里的重点是CHM和CSLM具有相似的读取语义,区别在于时间复杂度。
从您的问题中,您似乎得出的结论是,只有插入ConcurrentHashMap
才是线程安全的。
根据我的理解,ConcurrentHashMap 保证了多个线程插入的线程安全性。因此,如果您的地图仅由多个线程同时填充,则没有问题。
你是怎么得出这个结论的?ConcurrentHashMap
文档的第一行意味着所有操作都是线程安全的:
支持检索的完全并发性和可调整的更新预期并发的哈希表。
此外,这意味着get()
操作可以保持比put()
操作更高的并发级别。
简单地说ConcurrentHashMap
没有您认为的检索问题。在大多数情况下,您应该使用ConcurrentHashMap
而不是ConcurrentSkipListMap
因为ConcurrentHashMap
的性能通常优于ConcurrentSkipListMap
。仅当需要具有可预测迭代顺序的ConcurrentMap
或需要NavigableMap
的功能时,才应使用CurrentSkipListMap
。
ConcurrentHashMap
和ConcurrentSkipListMap
在使用什么同步原语方面存在差异。 ConcurrentHashMap
,以及基于非阻塞Unsafe
的 CAS 操作,使用阻塞synchronized
构造(也称为内部监视器,它使用 monitorenter
/monitorexit
字节码)来访问其"bins",而ConcurrentSkipListMap
仅使用非阻塞同步。因此,可以说,从这个意义上说,ConcurrentSkipListMap
更先进。
这可能看起来是一个相当微不足道的差异,几乎不会影响性能和其他一切(特别是在Java 7中synchronized
块性能显着提高时),但是在Java 19+中引入虚拟线程可能会改变游戏规则。根据 JEP 425,如果虚拟线程执行synchronized
块,则可能会固定虚拟线程:
通过修改同步块来避免频繁和长期的固定或频繁运行并保护可能较长的 I/O 的方法改用
java.util.concurrent.locks.ReentrantLock
的操作。
DZone的一篇文章讨论了这种替换。
另一方面,虚拟线程技术目前还不成熟,在我看来,没有必要急于用ReentrantLock
、AQS或类似的东西替换应用程序中直接或间接使用synchronized
块。