ConcurrentHashMap是如何提高并发时的吞吐性能(一)
guibin.beijing@gmail.com
为并发吞吐性能所做的优化
ConcurrentHashMap使用了一些技巧来获取高的并发性能,同时避免了锁。这些技巧包括:
- 为不同的Hash bucket(所谓hash bucket即不同范围的key的hash值)使用多个写锁;
- 利用JMM(Java Memory Model,java内存模型)的不确定性使得持有锁的时间最小化,或者从根本上避免使用锁。
ConcurrentHashMap为最常用的场景进行了优化,比如获取一个已经存在于Map中的值。事实上,绝大多数成功的get()操作在运行中根本就没有使用锁,这是因为利用了JMM的不确定性。
多个写锁
回忆一下HashTable的线程安全是因为使用了一个单独的全部Map范围的锁,这个锁在所有的插入、删除、查询操作中都会持有,甚至在使用Iterator遍历整个Map时也会持有这个单独的锁。当锁被一个线程持有时,就能够防止其他线程访问该Map,即便其他线程都处于闲置状态。这种单个锁的机制极大的限制了并发的性能。
而ConcurrentHashMap抛弃了仅仅使用整个Map范围的一个锁的机制,取而代之的是使用了32个锁,每个锁会负责hash bucket的一个子集(即负责一部分key的hash值范围)。而且这些锁仅仅被那些更改Map内容的操作使用,比如put(), remove()。拥有32个单独的锁意味时最多可以有32个线程同时修改Map,这并不是说如果有少于32个线程同时修改Map而没有线程被阻塞,32仅仅是理论上的并发写操作的极限,在实际中一般不一定会达到。在目前的软硬件条件下,对多数程序而言,32个锁总比一个锁要好。
Map范围的锁
32个独立的锁,每个锁负责hash bucket的一个子集,这意味着如果有些排他的访问操作,就需要获得全部的32个锁,比如rehashing(即扩充hash bucket的数量,当HashMap的key增长时重新分布key元素)就必须是排他的访问。但是JAVA语言本身没有提供一种简单的方式来获取变长的锁,由于这种操作不会频繁发生,那么ConcurrentHashMap是使用递归实现Map范围的锁。
JMM(Java内存模型)概览
JMM就是Java内存模型,它定义了Java的线程之间如何通过内存进行交互。简单说就是: JVM中存在一个主内存(Main Memory or Java Heap Memory),JAVA中所有变量都储存在主存中,对于所有线程都是共享的。每个线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。工作内存里的变量, 在多核CPU的情况下, 将大部分储存于处理器高速缓存中, 高速缓存在不经过内存时, 所以线程之间也是不可见的。详细的关于JMM的介绍会在后面的文章中。
大家都知道,CPU尽量使用自己的寄存器内部的数据,这样会极大的提高性能。JAVA规范(JLS-Java Language Specification)中规定了一些内存操作不必立即被其他线程发现,并且还提供了两个语言层面的机制来确保在多个线程之间保持内存操作的一致性:synchronized和volatile。根据JSL中的描述"In the absence of explicit synchronization, an implementation is free to update the main memory in an order that may be surprising." (在没有显示的同步时,一个操作可以以一种令人惊讶的方式自由的更新主存。)这意思是说:没有同步时,在一个线程中写操作的操作顺序也许和另外一个线程的写操作顺序不一样,并且更新内存变量会在不确定的时间之后被其他线程发现。
而使用synchronized的最根本的原因就是确保线程访问关键代码段的原子性。synchronized实际上提供了三个功能atomicity, visibility, and ordering(原子性,可见性和顺序行)。所谓原子性很好理解也很直接,就是确保不同线程再次进入同一区域的互斥性,防止在同一时刻有多于一个线程可以访问到被保护的代码段。很不幸的是许多的文章只强调了synchronized的原子性方面,而不说其他两个方面。在JMM中,同步扮演了一个重要的角色,当获取和释放monitor(锁)的时候,同步操作使得JVM执行了内存屏障(execute memory barriers)。
当一个线程获取一个锁时,它便执行了一次内存读屏障(read barriers)——所谓内存屏障就是使得任何其他线程缓存的本地内存(CPU内部缓存或者CPU寄存器)无效,然后使得其他所有线程所在的CPU重新从主存(内存)读取这些变量的值。同样,在释放锁的时候,所有其他线程均执行了一次内存写屏障(write barriers)——Flush任何已经更改的变量到主存(内存)。互斥和内存屏障的结合意味着只要程序遵循正确的同步规则(这个同步规则就是:同步任何被写的变量会下次被另一个线程正确的读;或者是同步任何读一个下一次会被另一个线程正确的更改变量),每个线程都会看到它所使用的共享变量的正确值。
如果在访问共享变量时没有同步,你会碰到一些奇怪的事情,一些改变会很快的反应到其他线程里,而其他一些更改则需要花费一些时间才能反应到其他线程中。这样的结果就会使得你如果不用synchronized,那么你不能确保你看到一致的内存视图(即相关变量在不同的线程中的值会不一致,也许有一些值是脏数据)。通常的方法,也是推荐的方法去避免这些脏数据当然是正确采用synchronized。在这种情况下,比如在广泛使用的基础类ConcurrentHashMap中就值得使用一些额外的专门知识和功夫去开发,以获得高性能。
参考资料:
http://www.ibm.com/developerworks/java/library/j-jtp08223/
http://www.cs.umd.edu/~pugh/java/memoryModel/
分享到:
相关推荐
Java——并发容器之ConcurrentHashMap;Java——并发容器之ConcurrentHashMap;Java——并发容器之ConcurrentHashMap;Java——并发容器之ConcurrentHashMap;Java——并发容器之ConcurrentHashMap;Java——并发...
HashTable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,即每次锁住整张表让线程独占,相当于所有线程进行读写时都去竞争一把锁,导致效率非常低下。ConcurrentHashMap可以做到读取数据不...
Java并发编程之ConcurrentHashMap Java并发编程之ConcurrentHashMap.pdf
ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment的结构,一个Segment其实就是一个类HashTable的结构,Segment内部维护了一个链表数组,我们用下面这一幅图来看下ConcurrentHashMap的内部结构...
ConcurrentHashMap使用了分段锁(Segment)来实现并发的读写操作,每个Segment都相当于一个小的HashMap,将整个哈希表分成多个部分。这样可以同时进行多个线程的并发读写操作,不会阻塞其他线程的访问。 需要注意的...
ConcurrentHashMap是J.U.C(java.util.concurrent包)的重要成员,它是HashMap的一个线程安全的、支持高效并发的版本。在默认理想状态下,ConcurrentHashMap可以支持16个线程执行并发写操作及任意数量线程的读操作。...
java源码剖析-ConcurrentHashMap
ConcurrentHashMap源码剖析
│ 高并发编程第一阶段38讲、给线程池增加自动扩充线程数量,以及闲时自动回收的功能.mp4 │ 高并发编程第一阶段39讲、课程结束,内容回顾,下季内容预告.mp4 │ ├─第二阶段 │ Java并发编程.png │ ppt+源码....
1. 聊聊并发(一)深入分析Volatile的实现原理 2. 聊聊并发(二)Java SE1.6中的Synchronized 3. 聊聊并发(三)Java线程池的分析和使用 4. 聊聊并发(四)深入分析ConcurrentHashMap 5. 聊聊并发(五)原子操作的...
ConcurrentHashMap具体是怎么实现线程安全的呢,肯定不可能是每个方法加synchronized,那样就变成了HashTable。
源码分析见我博文:http://blog.csdn.net/wabiaozia/article/details/50684556
Java利用ConcurrentHashMap实现本地缓存demo; 基本功能有缓存有效期、缓存最大数、缓存存入记录、清理线程、过期算法删除缓存、LRU算法删除、获取缓存值等功能。 复制到本地项目的时候,记得改包路径哦~
主要为大家详细分析了Java并发系列之ConcurrentHashMap源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
第三章 并发容器ConcurrentHashMap 46 一、JDK1.7中原理和实现 47 1、分段锁机制 47 2、ConcurrentHashMap中的数据结构 47 3、ConcurrentHashMap初始化 48 4、ConcurrentHashMap的操作 51 二、JDK1.8中原理和实现 54...
高薪程序员面试题精讲系列49之说说ConcurrentHashMap#put方法的源码及数。。。.pdf,这是一份不错的文件
Java 多线程与并发(13_26)-JUC集合_ ConcurrentHashMap详解
computeIfAbsent方法会初始化一个ReservationNode来占位,它会等待计算完毕后替换当前的占位对象。 这时候ConcurrentHashMap达到容量扩容而忽略了ReservationNode情况,调用put的时候在synchronized(f)没有对...