有些操作需要涉及到多个段,比如说size(), containsValaue()。先来看下size()方法:
Java代码 <embed height="15" width="14" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" allowscriptaccess="always" quality="high" flashvars="clipboard=%20%20%20%20public%20int%20size()%20%7B%0A%20%20%20%20%20%20%20%20final%20Segment%3CK%2CV%3E%5B%5D%20segments%20%3D%20this.segments%3B%0A%20%20%20%20%20%20%20%20long%20sum%20%3D%200%3B%0A%20%20%20%20%20%20%20%20long%20check%20%3D%200%3B%0A%20%20%20%20%20%20%20%20int%5B%5D%20mc%20%3D%20new%20int%5Bsegments.length%5D%3B%0A%20%20%20%20%20%20%20%20%2F%2F%20Try%20a%20few%20times%20to%20get%20accurate%20count.%20On%20failure%20due%20to%0A%20%20%20%20%20%20%20%20%2F%2F%20continuous%20async%20changes%20in%20table%2C%20resort%20to%20locking.%0A%20%20%20%20%20%20%20%20for%20(int%20k%20%3D%200%3B%20k%20%3C%20RETRIES_BEFORE_LOCK%3B%20%2B%2Bk)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20check%20%3D%200%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20sum%20%3D%200%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20int%20mcsum%20%3D%200%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20segments.length%3B%20%2B%2Bi)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sum%20%2B%3D%20segments%5Bi%5D.count%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mcsum%20%2B%3D%20mc%5Bi%5D%20%3D%20segments%5Bi%5D.modCount%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(mcsum%20!%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20segments.length%3B%20%2B%2Bi)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20check%20%2B%3D%20segments%5Bi%5D.count%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(mc%5Bi%5D%20!%3D%20segments%5Bi%5D.modCount)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20check%20%3D%20-1%3B%20%2F%2F%20force%20retry%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(check%20%3D%3D%20sum)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20if%20(check%20!%3D%20sum)%20%7B%20%2F%2F%20Resort%20to%20locking%20all%20segments%0A%20%20%20%20%20%20%20%20%20%20%20%20sum%20%3D%200%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20segments.length%3B%20%2B%2Bi)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20segments%5Bi%5D.lock()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20segments.length%3B%20%2B%2Bi)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sum%20%2B%3D%20segments%5Bi%5D.count%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20segments.length%3B%20%2B%2Bi)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20segments%5Bi%5D.unlock()%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20if%20(sum%20%3E%20Integer.MAX_VALUE)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20Integer.MAX_VALUE%3B%0A%20%20%20%20%20%20%20%20else%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20(int)sum%3B%0A%20%20%20%20%7D" src="http://www.javaeye.com/javascripts/syntaxhighlighter/clipboard_new.swf" lk_mediaid="lk_juiceapp_mediaPopup_1236652704328" lk_media="yes">
size方法主要思路是先在没有锁的情况下对所有段大小求和,如果不能成功(这是因为遍历过程中可能有其它线程正在对已经遍历过的段进行结构性更 新),最多执行RETRIES_BEFORE_LOCK次,如果还不成功就在持有所有段锁的情况下再对所有段大小求和。在没有锁的情况下主要是利用 Segment中的modCount进行检测,在遍历过程中保存每个Segment的modCount,遍历完成之后再检测每个Segment的 modCount有没有改变,如果有改变表示有其它线程正在对Segment进行结构性并发更新,需要重新计算。
其实这种方式是存在问题的,在第一个内层for循环中,在这两条语句sum += segments[i].count; mcsum += mc[i] = segments[i].modCount;之间,其它线程可能正在对Segment进行结构性的修改,导致segments[i].count和 segments[i].modCount读取的数据并不一致。这可能使size()方法返回任何时候都不曾存在的大小,很奇怪javadoc居然没有明 确标出这一点,可能是因为这个时间窗口太小了吧。size()的实现还有一点需要注意,必须要先segments[i].count,才能 segments[i].modCount,这是因为segment[i].count是对volatile变量的访问,接下来 segments[i].modCount才能得到几乎最新的值(前面我已经说了为什么只是“几乎”了)。这点在containsValue方法中得到了 淋漓尽致的展现:
Java代码 <embed height="15" width="14" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" allowscriptaccess="always" quality="high" flashvars="clipboard=%20%20%20%20public%20boolean%20containsValue(Object%20value)%20%7B%0A%20%20%20%20%20%20%20%20if%20(value%20%3D%3D%20null)%0A%20%20%20%20%20%20%20%20%20%20%20%20throw%20new%20NullPointerException()%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20See%20explanation%20of%20modCount%20use%20above%0A%0A%20%20%20%20%20%20%20%20final%20Segment%3CK%2CV%3E%5B%5D%20segments%20%3D%20this.segments%3B%0A%20%20%20%20%20%20%20%20int%5B%5D%20mc%20%3D%20new%20int%5Bsegments.length%5D%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Try%20a%20few%20times%20without%20locking%0A%20%20%20%20%20%20%20%20for%20(int%20k%20%3D%200%3B%20k%20%3C%20RETRIES_BEFORE_LOCK%3B%20%2B%2Bk)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20int%20sum%20%3D%200%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20int%20mcsum%20%3D%200%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20segments.length%3B%20%2B%2Bi)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20int%20c%20%3D%20segments%5Bi%5D.count%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mcsum%20%2B%3D%20mc%5Bi%5D%20%3D%20segments%5Bi%5D.modCount%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(segments%5Bi%5D.containsValue(value))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20boolean%20cleanSweep%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(mcsum%20!%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20segments.length%3B%20%2B%2Bi)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20int%20c%20%3D%20segments%5Bi%5D.count%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(mc%5Bi%5D%20!%3D%20segments%5Bi%5D.modCount)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cleanSweep%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(cleanSweep)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%2F%2F%20Resort%20to%20locking%20all%20segments%0A%20%20%20%20%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20segments.length%3B%20%2B%2Bi)%0A%20%20%20%20%20%20%20%20%20%20%20%20segments%5Bi%5D.lock()%3B%0A%20%20%20%20%20%20%20%20boolean%20found%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20segments.length%3B%20%2B%2Bi)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(segments%5Bi%5D.containsValue(value))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20found%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%20finally%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20segments.length%3B%20%2B%2Bi)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20segments%5Bi%5D.unlock()%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20return%20found%3B%0A%20%20%20%20%7D" src="http://www.javaeye.com/javascripts/syntaxhighlighter/clipboard_new.swf" lk_mediaid="lk_juiceapp_mediaPopup_1236652704334" lk_media="yes">
同样注意内层的第一个for循环,里面有语句int c = segments[i].count; 但是c却从来没有被使用过,即使如此,编译器也不能做优化将这条语句去掉,因为存在对volatile变量count的读取,这条语句存在的唯一目的就是 保证segments[i].modCount读取到几乎最新的值。关于containsValue方法的其它部分就不分析了,它和size方法差不多。
跨段方法中还有一个isEmpty()方法,其实现比size()方法还要简单,也不介绍了。最后简单地介绍下迭代方法,如keySet(), values(), entrySet()方法,这些方法都返回相应的迭代器,所有迭代器都继承于Hash_Iterator类(提交时居然提醒我不能包含shIt,只得加了 下划线),里实现了主要的方法。其结构是:
Java代码nextSegmentIndex是段的索引,nextTableIndex是nextSegmentIndex对应段中中hash链的索 引,currentTable是nextSegmentIndex对应段的table。调用next方法时主要是调用了advance方法:
Java代码 <embed height="15" width="14" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" allowscriptaccess="always" quality="high" flashvars="clipboard=%20%20%20%20%20%20%20%20final%20void%20advance()%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(nextEntry%20!%3D%20null%20%26%26%20(nextEntry%20%3D%20nextEntry.next)%20!%3D%20null)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20(nextTableIndex%20%3E%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(%20(nextEntry%20%3D%20currentTable%5BnextTableIndex--%5D)%20!%3D%20null)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20(nextSegmentIndex%20%3E%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Segment%3CK%2CV%3E%20seg%20%3D%20segments%5BnextSegmentIndex--%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(seg.count%20!%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20currentTable%20%3D%20seg.table%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20j%20%3D%20currentTable.length%20-%201%3B%20j%20%3E%3D%200%3B%20--j)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(%20(nextEntry%20%3D%20currentTable%5Bj%5D)%20!%3D%20null)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nextTableIndex%20%3D%20j%20-%201%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D" src="http://www.javaeye.com/javascripts/syntaxhighlighter/clipboard_new.swf" lk_mediaid="lk_juiceapp_mediaPopup_1236652704346" lk_media="yes">
不想再多介绍了,唯一需要注意的是跳到下一个段时,一定要先读取下一个段的count变量。
这种迭代方式的主要效果是不会抛出ConcurrentModificationException。一旦获取到下一个段的table,也就意味着 这个段的头结点在迭代过程中就确定了,在迭代过程中就不能反映对这个段节点并发的删除和添加,对于节点的更新是能够反映的,因为节点的值是一个 volatile变量。
结束语
ConcurrentHashMap是一个支持高并发的高性能的HashMap实现,它支持完全并发的读以及一定程度并发的写。 ConcurrentHashMap的实现也是很精巧,充分利用了最新的JMM规范,值得学习,却不值得模仿。最后由于本人水平有限,对大师的作品难免有 误解,如果存在,还望大牛们不吝指出。
参考文章:
http://www.ibm.com/developerworks/java/library/j-jtp08223/,这个是讨论的是Doug Lea's util.concurrent包中的ConcurrentHashMap的实现,不过大致思想是一致的。
http://floatingpoint.tinou.com/2008/09/performance-optimization-in-concurrenthashmap.html