http://www.importnew.com/15845.htmlhtml
ConcurrentHashMap融合了hashtable和hashmap两者的优点。java
hashtable是作了同步的,hashmap未考虑同步。因此hashmap在单线程状况下效率较高。hashtable在的多线程状况下,同步操做能保证程序执行的正确性。编程
hashtable采用synchronized关键字进行同步,每次同步执行的时候都要锁住整个结构。看下图:数组
(程序使用一个公共锁同步每一个方法,并严格地限制只能有一个线程能够同时访问容器——《java并发编程实践》)数据结构
ConcurrentHashMap正是为了解决这个问题而诞生的。多线程
ConcurrentHashMap锁的方式是稍微细粒度的。 ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等经常使用操做只锁当前须要用到的桶。并发
(ConcurrentHashMap使用地是更加细化的锁机制,分离锁。任何数量的读线程能够并发访问Map,读者和写着能够并发访问Map,而且有限数量的写线程能够并发修改Map——《java并发编程实践》 )函数
试想,原来 只能一个线程进入,如今却能同时16个写线程进入(写线程才须要锁定,而读线程几乎不受限制,以后会提到),并发性的提高是显而易见的。(1)性能
更使人惊讶的是ConcurrentHashMap的读取并发,由于在读取的大多数时候都没有用到锁定,因此读取操做几乎是彻底的并发操做,而写操做锁定的粒度又很是细,比起以前又更加快速(这一点在桶更多时表现得更明显些)。只有在求size等操做时才须要锁定整个表。spa
而在迭代时,ConcurrentHashMap使用了不一样于传统集合的快速失败迭代器的另外一种迭代方式,咱们称为弱一致迭代器。在这种迭代方式中,当iterator被建立后集合再发生改变就再也不是抛出 ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数 据,iterator完成后再将头指针替换为新的数据,这样iterator线程可使用原来老的数据,而写线程也能够并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提高的关键。(2)(复制容器)
下面分析ConcurrentHashMap的源码。主要是分析其中的Segment。由于操做基本上都是在Segment上的。先看Segment内部数据的定义。
从上图能够看出,很重要的一个是table变量。是一个HashEntry的数组。Segment就是把数据存放在这个数组中的。除了这个量,还有诸如loadfactor、modcount等变量。
看segment的get 函数的实现:
加上hashentry的代码:
能够看出,hashentry是一个链表型的数据结构。
在segment的get函数中,经过getFirst函数获得第一个值,而后就是经过这个值的next,一路找到想要的那个对象。若是不空,则返回。若是为空,则多是其余线程正在修改节点。好比上面说的弱一致迭代器在将指针更改成新值的过程。而以前的 get操做都未进行锁定,根据bernstein条件,读后写或写后读都会引发数据的不一致,因此这里要对这个e从新上锁再读一遍,以保证获得的是正确值。readValueUnderLock中就是用了lock()进行加锁。
put操做已开始就锁住了整个segment。这是由于修改操做时不能并发的。
一样,remove操做也是如此(相似put,一开始就锁住真个segment)。
但要注意一点区别,中间那个for循环是作什么用的呢?(截图未彻底,能够本身找找代码查看一下)。从代码来看,就是将定位以后的全部entry克隆并拼回前面去,但有必要吗?每次删除一个元素就要将那以前的元素克隆一遍?这点实际上是由entry的不变性来决定的,仔细观察entry定义,发现除了value,其余 全部属性都是用final来修饰的,这意味着在第一次设置了next域以后便不能再改变它,取而代之的是将它以前的节点全都克隆一次。至于entry为何要设置为不变性,这跟不变性的访问不须要同步从而节省时间有关。