Java理论与实践: 构建一个更好的HashMap - 编程入门网
Java理论与实践: 构建一个更好的HashMap时间:2010-12-21 IBM Brian GoetzConcurrentHashMap 是 Doug Lea的 util.concurrent 包的一部分,它提供 比Hashtable 或者 synchronizedMap 更高程度的并发性。而且,对于大多数成 功的 get() 操作它会设法避免完全锁定,其结果就是使得并发应用程序有着非 常好的吞吐量。这个月,BrianGoetz 仔细分析了 ConcurrentHashMap的代码, 并探讨 Doug Lea 是如何在不损失线程安全的情况下取得这么骄人成绩的。 在7月份的那期 Java理论与实践(“ Concurrent collections classes”) 中,我们简单地回顾了可伸缩性的瓶颈,并讨论了怎么用共享数据结构的方法获 得更高的并发性和吞吐量。有时候学习的最好方法是分析专家的成果,所以这个 月我们将分析 Doug Lea的 util.concurrent 包中的 ConcurrentHashMap的实现 。JSR 133 将指定 ConcurrentHashMap的一个版本,该版本针对 Java 内存模型 (JMM)作了优化,它将包含在JDK 1.5的 java.util.concurrent 包中。 util.concurrent 中的版本在老的和新的内存模型中都已通过线程安全审核。 针对吞吐量进行优化 ConcurrentHashMap 使用了几个技巧来获得高程度的并发以及避免锁定,包 括为不同的 hash bucket(桶)使用多个写锁和使用 JMM的不确定性来最小化锁 被保持的时间――或者根本避免获取锁。对于大多数一般用法来说它是经过优化 的,这些用法往往会检索一个很可能在map 中已经存在的值。事实上,多数成功 的 get() 操作根本不需要任何锁定就能运行。(警告:不要自己试图这样做! 想比 JMM 聪明不像看上去的那么容易。 util.concurrent 类是由并发专家编写 的,并且在JMM 安全性方面经过了严格的同行评审。) 多个写锁 我们可以回想一下,Hashtable(或者替代方案 Collections.synchronizedMap )的可伸缩性的主要障碍是它使用了一个 map 范围(map-wide)的锁,为了保证插入、删除或者检索操作的完整性必须保持这 样一个锁,而且有时候甚至还要为了保证迭代遍历操作的完整性保持这样一个锁 。这样一来,只要锁被保持,就从根本上阻止了其他线程访问 Map,即使处理器 有空闲也不能访问,这样大大地限制了并发性。 ConcurrentHashMap 摒弃了单一的 map 范围的锁,取而代之的是由 32 个锁 组成的集合,其中每个锁负责保护 hash bucket的一个子集。锁主要由变化性操 作( put() 和remove() )使用。具有 32 个独立的锁意味着最多可以有 32 个 线程可以同时修改 map。这并不一定是说在并发地对 map 进行写操作的线程数 少于 32 时,另外的写操作不会被阻塞――32 对于写线程来说是理论上的并发 限制数目,但是实际上可能达不到这个值。但是,32 依然比 1 要好得多,而且 对于运行于目前这一代的计算机系统上的大多数应用程序来说已经足够了。   map 范围的操作 有 32 个独立的锁,其中每个锁保护 hash bucket的一个子集,这样需要独 占访问 map的操作就必须获得所有32个锁。一些 map 范围的操作,比如说 size() 和isEmpty(), 也许能够不用一次锁整个 map(通过适当地限定这些操 作的语义),但是有些操作,比如 map 重排(扩大 hash bucket的数量,随着 map的增长重新分布元素),则必须保证独占访问。Java 语言不提供用于获取可 变大小的锁集合的简便方法。必须这么做的情况很少见,一旦碰到这种情况,可 以用递归方法来实现。 JMM概述 在进入 put() 、 get() 和remove()的实现之前,让我们先简单地看一下 JMM。JMM 掌管着一个线程对内存的动作(读和写)影响其他线程对内存的动作 的方式。由于使用处理器寄存器和预处理 cache 来提高内存访问速度带来的性 能提升,Java 语言规范(JLS)允许一些内存操作并不对 |
凌众科技专业提供服务器租用、服务器托管、企业邮局、虚拟主机等服务,公司网站:http://www.lingzhong.cn 为了给广大客户了解更多的技术信息,本技术文章收集来源于网络,凌众科技尊重文章作者的版权,如果有涉及你的版权有必要删除你的文章,请和我们联系。以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢! |