java.util.concurrent.ConcurrentHashMap是Java5中支持高并发、高吞吐量的线程安全HashMap实现。
<wbr style="line-height:25px">实现原理</wbr><wbr style="line-height:25px"><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">锁分离(LockStripping)<br style="line-height:25px"></wbr></span><wbr style="line-height:25px"><span style="color:#003366; line-height:25px"></span><span style="color:#993300; line-height:25px">ConcurrentHashMap</span><span style="color:#003366; line-height:25px">允许多个修改操作并发进行,其关键在于使用了锁分离技术。<br style="line-height:25px"> 它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,<br style="line-height:25px"> 每个段其实就是一个小的hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。<br style="line-height:25px"> 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,<br style="line-height:25px"> 这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,<br style="line-height:25px"> 在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,<br style="line-height:25px"> 但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。<br style="line-height:25px"> 可以确保不会出现死锁,因为获得锁的顺序是固定的。不变性是多线程编程占有很重要的地位,下面还要谈到。</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注1</wbr></span><wbr style="line-height:25px">:即使数组成员也是final的,也不能解决死锁吧。<br style="line-height:25px"> 这里解决死锁注要是“按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁“来解决的。<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Thesegments,eachofwhichisaspecializedhashtable<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#0000ff; line-height:25px">finalSegment<K,V>[]segments;</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">不变(Immutable)和易变(Volatile)<br style="line-height:25px"></wbr></span><wbr style="line-height:25px"><span style="color:#993300; line-height:25px"><wbr style="line-height:25px">ConcurrentHashMap</wbr></span><span style="color:#003366; line-height:25px">完全允许多个读操作并发进行,读操作并不需要加锁</span><wbr style="line-height:25px"><span style="color:#003366; line-height:25px">。</span><br style="line-height:25px"> 如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。<br style="line-height:25px"><span style="color:#993300; line-height:25px">ConcurrentHashMap</span>实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,其结构如下所示:<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注1</wbr></span><wbr style="line-height:25px">:注意”读操作“也是要从hash链的头开始寻找的。所以在HashMap中”hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据“<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">staticfinalclass</span><span style="color:#3366ff; line-height:25px">HashEntry<K,V>{<br style="line-height:25px"> finalKkey;<br style="line-height:25px"> finalinthash;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#3366ff; line-height:25px">Vvalue;<br style="line-height:25px"> finalHashEntry<K,V>next;<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">因为这需要修改next引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">但是对于remove操作,</span><span style="color:#0000ff; line-height:25px">可<wbr style="line-height:25px">能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍</wbr></span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">,</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">将</span><span style="color:#993300; line-height:25px">value</span><span style="color:#000080; line-height:25px">设置成</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">,这</span><span style="color:#993300; line-height:25px">避免了加锁</span><span style="color:#000080; line-height:25px">。</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意1</wbr></span><wbr style="line-height:25px">:”对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍“<br style="line-height:25px"> 这操作的代价应该不下哦。这个也应该是它和HashTable比的一个劣势。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">其它</wbr></span><wbr style="line-height:25px"><br style="line-height:25px"><span style="color:#000080; line-height:25px">为了加快定位段以及段中hash槽的速度,每个段hash槽的的个数都是2^n,这使得通过位运算就可以定位段和段中hash槽的位置。<br style="line-height:25px"> 当并发级别为默认值16时,也就是段的个数,hash值的高4位决定分配在哪个段中。<br style="line-height:25px"> 但是我们也不要忘记《算法导论》给我们的教训:hash槽的的个数不应该是2^n,这可能导致hash槽分配不均,<br style="line-height:25px"> 这需要对hash值重新再hash一次。</span><br style="line-height:25px"> 这是重新hash的算法,还比较复杂,我也懒得去理解了。<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#993300; line-height:25px">privatestaticint</span><span style="color:#3366ff; line-height:25px"></span><span style="color:#ff6600; line-height:25px">hash</span><span style="color:#3366ff; line-height:25px">(inth){</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">//Spreadbitstoregularizebothsegmentandindexlocations,<br style="line-height:25px"> //usingvariantofsingle-wordWang/Jenkinshash.</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">h+=(h<<15)^0xffffcd7d;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">h^=(h>>>10);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">h+=(h<<3);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">h^=(h>>>6);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">h+=(h<<2)+(h<<14);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">h^(h>>>16);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"> 这是定位段的方法:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#0000ff; line-height:25px"></span><span style="color:#993300; line-height:25px">final</span><span style="color:#0000ff; line-height:25px">Segment<K,V></span><span style="color:#ff6600; line-height:25px">segmentFor</span><span style="color:#0000ff; line-height:25px">(inthash){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#0000ff; line-height:25px">segments[(hash>>>segmentShift)&segmentMask];<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="line-height:25px">数据结构</span><br style="line-height:25px"> 关于Hash表的基础数据结构,这里不想做过多的探讨。Hash表的一个很重要方面就是如何解决hash冲突,<br style="line-height:25px"> ConcurrentHashMap和HashMap使用相同的方式,都是将hash值相同的节点放在一个hash链中。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">与HashMap不同的是,ConcurrentHashMap使用多个子Hash表,也就是段(Segment)。</wbr></span><wbr style="line-height:25px"><br style="line-height:25px"> 下面是ConcurrentHashMap的数据成员:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#993300; line-height:25px">publicclass</span><span style="color:#3366ff; line-height:25px"></span><span style="color:#ff6600; line-height:25px">ConcurrentHashMap<K,V></span><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">extends</span><span style="color:#3366ff; line-height:25px">AbstractMap<K,V></span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">implementsConcurrentMap<K,V>,Serializable{</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Maskvalueforindexingintosegments.Theupperbitsofa<br style="line-height:25px"> *key'shashcodeareusedtochoosethesegment.<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">finalintsegmentMask;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Shiftvalueforindexingwithinsegments.<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">finalintsegmentShift;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Thesegments,eachofwhichisaspecializedhashtable<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">finalSegment<K,V>[]segments;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">所有的成员都是final的,其中segmentMask和segmentShift主要是为了定位段,参见上面的segmentFor方法。<br style="line-height:25px"> 每个Segment相当于一个子Hash表,它的数据成员如下</span>:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">staticfinalclass</span><span style="color:#3366ff; line-height:25px"></span><span style="color:#ff6600; line-height:25px">Segment<K,V></span><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">extends</span><span style="color:#3366ff; line-height:25px">ReentrantLockimplementsSerializable{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">privatestaticfinallongserialVersionUID=2249069246763182397L;</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Thenumberofelementsinthissegment'sregion.<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">transientvolatileint</span><span style="color:#3366ff; line-height:25px">count;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Numberofupdatesthatalterthesizeofthetable.Thisis<br style="line-height:25px"> *usedduringbulk-readmethodstomakesuretheyseea<br style="line-height:25px"> *consistentsnapshot:IfmodCountschangeduringatraversal<br style="line-height:25px"> *ofsegmentscomputingsizeorcheckingcontainsValue,then<br style="line-height:25px"> *wemighthaveaninconsistentviewofstateso(usually)<br style="line-height:25px"> *mustretry.<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">transientint</span><span style="color:#3366ff; line-height:25px">modCount;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Thetableisrehashedwhenitssizeexceedsthisthreshold.<br style="line-height:25px"> *(Thevalueofthisfieldisalways<tt>(int)(capacity*<br style="line-height:25px"> *loadFactor)</tt>.)<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">transientint</span><span style="color:#3366ff; line-height:25px">threshold;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Theper-segmenttable.<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">transientvolatile</span><span style="color:#3366ff; line-height:25px">HashEntry<K,V>[]table;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Theloadfactorforthehashtable.Eventhoughthisvalue<br style="line-height:25px"> *issameforallsegments,itisreplicatedtoavoidneeding<br style="line-height:25px"> *linkstoouterobject.<br style="line-height:25px"> *@serial<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">finalfloat</span><span style="color:#3366ff; line-height:25px">loadFactor;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#000080; line-height:25px"></span><span style="color:#ff9900; line-height:25px">count</span><span style="color:#000080; line-height:25px">用来统计该段数据的个数,它是</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">,它用来协调修改和读取操作,以保证读取操作能够读取到几乎最新的修改。<br style="line-height:25px"> 协调方式是这样的,每次修改操作做了结构上的改变,如增加/删除节点(修改节点的值不算结构上的改变),都要写</span><span style="color:#0000ff; line-height:25px">count</span><span style="color:#000080; line-height:25px">值,<br style="line-height:25px"> 每次读取操作开始都要读取count的值。这利用了Java5中对volatile语义的增强,对同一个volatile变量的写和读存在happens-before关系。<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">modCount</span><span style="color:#000080; line-height:25px">统计段结构改变的次数,主要是为了检测对多个段进行遍历过程中某个段是否发生改变,在讲述跨段操作时会还会详述。<br style="line-height:25px"></span><span style="color:#ff6600; line-height:25px">threashold</span><span style="color:#000080; line-height:25px">用来表示需要进行rehash的界限值。</span><span style="color:#ff9900; line-height:25px">table</span><span style="color:#000080; line-height:25px">数组存储段中节点,每个数组元素是个</span><span style="color:#ff9900; line-height:25px">hash</span><span style="color:#000080; line-height:25px">链,用</span><span style="color:#99cc00; line-height:25px">HashEntry</span><span style="color:#000080; line-height:25px">表示。<br style="line-height:25px"> table也是</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">,这使得能够读取到最新的table值而不需要同步。</span><span style="color:#993300; line-height:25px">loadFactor</span><span style="color:#000080; line-height:25px">表示负载因子</span>。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">实现细节<br style="line-height:25px"> 修改操作<br style="line-height:25px"></wbr></span><wbr style="line-height:25px">先来看下删除操作remove(key)。<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">publicVremove(Objectkey){<br style="line-height:25px"> inthash=hash(key.hashCode());<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">segmentFor(hash).remove(key,hash,null);<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">整个操作是先定位到段,然后委托给段的remove操作。当多个删除操作并发进行时,只要它们所在的段不相同,它们就可以同时进行。</span><br style="line-height:25px"> 下面是Segment的remove方法实现:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">V</span><span style="color:#ff6600; line-height:25px">remove</span><span style="color:#3366ff; line-height:25px">(Objectkey,inthash,Objectvalue){<br style="line-height:25px"> lock();<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">try</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intc=count-1;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>[]tab=table;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intindex=hash&(tab.length-1);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>first=tab[index];</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>e=first;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">while(e!=null&&(e.hash!=hash||!key.equals(e.key)))</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">e=e.next;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">VoldValue=null;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(e!=null){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">Vv=e.value;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">if(value==null||value.equals(v)){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">oldValue=v;</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">//Allentriesfollowingremovednodecanstay<br style="line-height:25px"> //inlist,butallprecedingonesneedtobe<br style="line-height:25px"> //cloned.</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">++modCount;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>newFirst=e.next;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(HashEntry<K,V>p=first;p!=e;p=p.next)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">newFirst=newHashEntry<K,V>(p.key,p.hash,</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">newFirst,p.value);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">tab[index]=newFirst;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">count=c;</span><span style="color:#808080; line-height:25px">//write-volatile</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">oldValue;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><span style="color:#993300; line-height:25px">finally</span><span style="color:#3366ff; line-height:25px">{<br style="line-height:25px"> unlock();<br style="line-height:25px"> }<br style="line-height:25px"> }</span><br style="line-height:25px"> 整个操作是在持有段锁的情况下执行的,空白行之前的行主要是定位到要删除的节点e。<br style="line-height:25px"> 接下来,如果不存在这个节点就直接返回null,否则就要将e前面的结点复制一遍,尾结点指向e的下一个结点。<br style="line-height:25px"> e后面的结点不需要复制,它们可以重用。<br style="line-height:25px"> 注:<wbr style="line-height:25px"><span style="color:#000080; line-height:25px">删除一个节点后,它前面的节点的顺序会颠倒<wbr style="line-height:25px">。比如对于节点1,2,3,4,5。如果删除了节点4,就变成了3,2,1,5.</wbr></span><br style="line-height:25px"> 整个remove实现并不复杂,但是需要注意如下几点。<br style="line-height:25px"><span style="color:#000080; line-height:25px">第一,当要删除的结点存在时,删除的最后一步操作要将count的值减一。这必须是最后一步操作,否则读取操作可能看不到之前对段所做的结构性修改。<br style="line-height:25px"> 第二,remove执行的开始就将table赋给一个局部变量tab,这是因为table是</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量,读写</span><span style="color:#ff6600; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量的开销很大。<br style="line-height:25px"> 因为编译器也不能对volatile变量的读写做任何优化。</span><br style="line-height:25px"> 接下来看put操作,同样地put操作也是委托给段的put方法。下面是段的put方法:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">V</span><span style="color:#ff6600; line-height:25px">put</span><span style="color:#3366ff; line-height:25px">(Kkey,inthash,Vvalue,booleanonlyIfAbsent){<br style="line-height:25px"> lock();<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">try</span><span style="color:#3366ff; line-height:25px">{<br style="line-height:25px"> intc=count;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(c++>threshold)</span><span style="color:#808080; line-height:25px">//ensurecapacity</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">rehash();</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>[]tab=table;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">int</span><span style="color:#3366ff; line-height:25px">index=hash&(tab.length-1);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#0000ff; line-height:25px">HashEntry<K,V>first=tab[index];</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>e=first;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">while</span><span style="color:#3366ff; line-height:25px">(e!=null&&(e.hash!=hash||!key.equals(e.key)))</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">e=e.next;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">VoldValue;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(e!=null){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">oldValue=e.value;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(!onlyIfAbsent)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">e.value=value;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">else</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">oldValue=null;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">++modCount;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">tab[index]=newHashEntry<K,V>(key,hash,first,value);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">count=c;</span><span style="color:#808080; line-height:25px">//write-volatile</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">oldValue;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><span style="color:#993300; line-height:25px">finally</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">unlock();</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">该方法也是在持有段锁的情况下执行的,首先判断是否需要rehash,需要就先</span><span style="color:#ff6600; line-height:25px">rehash</span><span style="color:#000080; line-height:25px">。<br style="line-height:25px"> 接着是找是否存在同样一个key的结点,如果存在就直接替换这个结点的值。否则创建一个新的结点并添加到hash链的头部,<br style="line-height:25px"> 这时一定要修改modCount和count的值,同样修改count的值一定要放在最后一步。<br style="line-height:25px"> put方法调用了rehash方法,reash方法实现得也很精巧,主要利用了table的大小为2^n,这里就不介绍了。<br style="line-height:25px"> 修改操作还有putAll和replace。putAll就是多次调用put方法,没什么好说的。replace甚至不用做结构上的更改,<br style="line-height:25px"> 实现要比put和delete要简单得多,理解了put和delete,理解replace就不在话下了,这里也不介绍了。</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">获取操作<br style="line-height:25px"></wbr></span><wbr style="line-height:25px">首先看下get操作,同样ConcurrentHashMap的get操作是直接委托给Segment的get方法,直接看Segment的get方法:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">V</span><span style="color:#ff6600; line-height:25px">get</span><span style="color:#3366ff; line-height:25px">(Objectkey,inthash){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(count!=0){</span><span style="color:#808080; line-height:25px">//read-volatile</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>e=getFirst(hash);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">while</span><span style="color:#3366ff; line-height:25px">(e!=null){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(e.hash==hash&&key.equals(e.key)){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">Vv=e.value;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(v!=null)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">v;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">readValueUnderLock(e);</span><span style="color:#808080; line-height:25px">//recheck</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">e=e.next;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">returnnull;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">get操作不需要锁。第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count变量,<br style="line-height:25px"> 通过这种机制保证get操作能够得到几乎最新的结构更新。对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是</span><span style="color:#ff9900; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">的,也能保证读取到最新的值。接下来就是对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。<br style="line-height:25px"> 对hash链进行遍历不需要加锁的原因在于链指针next是final的。但是头指针却不是final的,这是通过getFirst(hash)方法返回,<br style="line-height:25px"> 也就是存在table数组中的值。这使得</span><span style="color:#0000ff; line-height:25px">getFirst(hash)</span><span style="color:#000080; line-height:25px">可能返回过时的头结点,例如,当执行get方法时,刚执行完getFirst(hash)之后,<br style="line-height:25px"> 另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。这是可以允许,通过对count变量的协调机制,<br style="line-height:25px"> get能读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。<br style="line-height:25px"> 最后,如果找到了所求的结点,判断它的值如果非空就直接返回,否则在有锁的状态下再读一次。<br style="line-height:25px"> 这种情况应当很罕见,一旦发生这种情况,ConcurrentHashMap采取的方式是在持有锁的情况下再读一遍,<br style="line-height:25px"> 这能够保证读到最新的值,并且一定不会为空值。</span><br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">V</span><span style="color:#ff6600; line-height:25px">readValueUnderLock</span><span style="color:#3366ff; line-height:25px">(HashEntry<K,V>e){<br style="line-height:25px"> lock();<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">try</span><span style="color:#3366ff; line-height:25px">{<br style="line-height:25px"> returne.value;<br style="line-height:25px"> }</span><span style="color:#993300; line-height:25px">finally</span><span style="color:#3366ff; line-height:25px">{<br style="line-height:25px"> unlock();<br style="line-height:25px"> }<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="color:#000080; line-height:25px"><wbr style="line-height:25px">所求的结点的值为空,什么情况才会发生呢<wbr style="line-height:25px">?</wbr></wbr></span>且readValueUnderLock(e)能生效呢?网上有两种比较常见的说法:<br style="line-height:25px"><span style="color:#000080; line-height:25px">1)另外一条线程在修改节点<br style="line-height:25px"> 2)HashEntry构造函数中对value的赋值以及对tab[index]的赋值可能被重新排序,或许源出自<a target="_blank" rel="nofollow" href="http://www.javaeye.com/topic/344876" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://www.javaeye.com/topic/344876</a><wbr style="line-height:25px"><br style="line-height:25px"> 查看</wbr></span><span style="color:#993300; line-height:25px">ConcurrentHashMap</span><span style="color:#000080; line-height:25px">源码能看到put方法其实是创建一个新的节点并以之前的第一个节点为这个节点的next节点<br style="line-height:25px"> (即这个新创建的节点为单向链表的第一个节点),然后</span><br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#0000ff; line-height:25px">tab[index]=newHashEntry<K,V>(key,hash,first,value);</span><br style="line-height:25px"> 执行赋值<br style="line-height:25px"> remove方法则是重新把整条单向链表(除被删除Entry)全部拷贝一次执行赋值<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(HashEntry<K,V>p=first;p!=e;p=p.next)<br style="line-height:25px"> newFirst=newHashEntry<K,V>(p.key,p.hash,<br style="line-height:25px"> newFirst,p.value);<br style="line-height:25px"> tab[index]=newFirst;</span><br style="line-height:25px"> 所有,既不是另外一条线程正在修改某节点,也不是重新排序。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">最后追踪到源码的注释</wbr></span><wbr style="line-height:25px">:<br style="line-height:25px"> 引用<br style="line-height:25px"><span style="color:#808080; line-height:25px">Thisispossibleonlyifa<br style="line-height:25px"> compilerhappenstoreorderaHashEntryinitializationwith<br style="line-height:25px"> itstableassignment,whichislegalundermemorymodel<br style="line-height:25px"> butisnotknowntoeveroccur.</span><br style="line-height:25px"> 原来还是Java内存模型的问题(Jmm),执行<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#0000ff; line-height:25px">tab[index]=newFirst</span><br style="line-height:25px"> 或<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#0000ff; line-height:25px">tab[index]=newHashEntry<K,V>(key,hash,first,value);</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">时,Entry已经被认为初始化ok并返回给table(在堆中分派一块空间),这是正好有其他线程调用到get方法,<br style="line-height:25px"> 获取到这个刚“实例化好”的单向链表,但由于</span><span style="color:#ff9900; line-height:25px">JMM初始化的无序性</span><span style="color:#000080; line-height:25px">,当你get这个链表Entry中的value时,<br style="line-height:25px"> 他有可能还没完全初始化好!或按注释所说不知道发生过这样的初始化但这是合法的。。。。<br style="line-height:25px"> 这和为什么Java的<wbr style="line-height:25px">单例模式不能使用双重检查加锁的原因类似<wbr style="line-height:25px">,</wbr></wbr></span><br style="line-height:25px"> 都是由于Java内存模型(JMM)的无序性。这里有部分介绍<a target="_blank" rel="nofollow" href="http://rjx2008.javaeye.com/admin/blogs/342474" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://rjx2008.javaeye.com/admin/blogs/342474</a><wbr style="line-height:25px"><br style="line-height:25px"> 另一个操作是containsKey,这个实现就要简单得多了,因为它不需要读取值:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">boolean</span><span style="color:#ff6600; line-height:25px">containsKey</span><span style="color:#3366ff; line-height:25px">(Objectkey,inthash){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(count!=0){//read-volatile<br style="line-height:25px"> HashEntry<K,V>e=getFirst(hash);<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">while</span><span style="color:#3366ff; line-height:25px">(e!=null){<br style="line-height:25px"> if(e.hash==hash&&key.equals(e.key))<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">true;<br style="line-height:25px"> e=e.next;<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">false;<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">跨段操作<br style="line-height:25px"></wbr></span><wbr style="line-height:25px">有些操作需要涉及到多个段,比如说size(),containsValaue()。先来看下size()方法:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">publicint</span><span style="color:#3366ff; line-height:25px">size(){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">final</span><span style="color:#3366ff; line-height:25px">Segment<K,V>[]segments=this.segments;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">long</span><span style="color:#3366ff; line-height:25px">sum=0;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">long</span><span style="color:#3366ff; line-height:25px">check=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">int[]mc=newint[segments.length];</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">//Tryafewtimestogetaccuratecount.Onfailuredueto<br style="line-height:25px"> //continuousasyncchangesintable,resorttolocking.</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(intk=0;k<RETRIES_BEFORE_LOCK;++k){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">check=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">sum=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intmcsum=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">sum+=segments[i]</span><span style="color:#3366ff; line-height:25px">.count;</span><br style="line-height:25px"><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px">mcsum+=mc[i]<wbr style="line-height:25px">=segments[i]</wbr></span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.modCount;<br style="line-height:25px"> }<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(mcsum!=0){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i){<br style="line-height:25px"> check+=segments[i]</span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.count;<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(mc[i]</span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">!=segments[i]</wbr></span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.modCount){<br style="line-height:25px"> check=-1;//forceretry<br style="line-height:25px"> break;<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> if(check==sum)<br style="line-height:25px"> break;<br style="line-height:25px"> }<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(check!=sum){</span><span style="color:#808080; line-height:25px">//Resorttolockingallsegments</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">sum=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i)<br style="line-height:25px"> segments</span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.lock();<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i)<br style="line-height:25px"> sum+=segments</span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.count;<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i)<br style="line-height:25px"> segments</span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.unlock();<br style="line-height:25px"> }<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(sum>Integer.MAX_VALUE)<br style="line-height:25px"> returnInteger.MAX_VALUE;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">else</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">(int)sum;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"></span><br style="line-height:25px"><span style="color:#000080; line-height:25px"><wbr style="line-height:25px">size方法主要思路是先在没有锁的情况下对所有段大小求和</wbr></span><wbr style="line-height:25px"><span style="color:#003366; line-height:25px">,如果不能成功</span><br style="line-height:25px"><span style="line-height:25px; color:rgb(0,51,102)"></span><span style="color:#003366; line-height:25px">(这是因为遍历过程中可能有其它线程正在对已经遍历过的段进行结构性更新),</span><br style="line-height:25px"><span style="color:#003366; line-height:25px">最多执行RETRIES_BEFORE_LOCK次,如果还不成功就在持有所有段锁的情况下再对所有段大小求和。</span><br style="line-height:25px"><span style="color:#003366; line-height:25px">在没有锁的情况下主要是利用Segment中的modCount进行检测,在遍历过程中保存每个Segment的modCount,</span><br style="line-height:25px"><span style="color:#003366; line-height:25px">遍历完成之后再检测每个Segment的modCount有没有改变,如果有改变表示有其它线程正在对Segment进行结构性并发更新,需要重新计算。</span><br style="line-height:25px"><span style="color:#003366; line-height:25px">其实</span>这种方式是存在问题的,在第一个内层for循环中,在这两条语句sum+=segments<wbr style="line-height:25px">.count;mcsum+=mc<wbr style="line-height:25px">=segments<wbr style="line-height:25px">.modCount;之间,<br style="line-height:25px"> 其它线程可能正在对Segment进行结构性的修改,导致segments<wbr style="line-height:25px">.count和segments<wbr style="line-height:25px">.modCount读取的数据并不一致。<br style="line-height:25px"> 这可能使size()方法返回任何时候都不曾存在的大小,很奇怪javadoc居然没有明确标出这一点,可能是因为这个时间窗口太小了吧。<br style="line-height:25px"><span style="color:#000080; line-height:25px"><wbr style="line-height:25px">size()的实现还有一点需要注意,必须要先segments<wbr style="line-height:25px">.count,才能segments<wbr style="line-height:25px">.modCount,这是因为segment<wbr style="line-height:25px">.count是对volatile变量的访问,<br style="line-height:25px"> 接下来segments<wbr style="line-height:25px">.modCount才能得到几乎最新的值</wbr></wbr></wbr></wbr></wbr></span><wbr style="line-height:25px">(前面我已经说了为什么只是“几乎”了)。<br style="line-height:25px"> 这点在containsValue方法中得到了淋漓尽致的展现:<br style="line-height:25px"> 关于size()的实现"必须要先segments<wbr style="line-height:25px">.count,才能segments<wbr style="line-height:25px">.modCount"的原因:<br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">写</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量和它之前的读写操作是不能</span><span style="color:#ff6600; line-height:25px">reorde</span><span style="color:#000080; line-height:25px">r的,读</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量和它之后的读写操作也是不能reorder的。<br style="line-height:25px"> 修改modCount发生在修改count之前,由于count是</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量,修改modCount不能和写count的操作reorder,读取count和它之后的操作,</span><span style="color:#000080; line-height:25px">比如读取modCount,不能reorder。有了这两个不能reorder才能保证读取了count之后,能读到线程在写count之前的写入的modCount值,这个modCount值是几乎最新的。如果在读</span><span style="color:#99cc00; line-height:25px">modCount</span><span style="color:#000080; line-height:25px">之前不读count,读modCount甚至可能会reorder到写modCount之前。<br style="line-height:25px"> 用reorder解释总是太复杂了,不如用</span><span style="color:#ff6600; line-height:25px">happens-before</span><span style="color:#000080; line-height:25px">来得简洁。当一个线程I对count的读时,它读到的值必定是另一个线程,<br style="line-height:25px"> 假设是线程II,最近对count的写。这个两个操作存在happens-before关系,即线程II对count的写happens-before线程I对count的读,<br style="line-height:25px"> 记作:II:W(count)<I:R(count)。单线程的happens-before规则,又有II:W(modCount)<II:W(count)(查看源代码会发现在写count之前必定有写modCount),<br style="line-height:25px"> 以及I:R(count)<I:R(modCount),根据传递规则有,II:W(modCount)<I:R(modCount),这就是说线程I至少能够读取到线程II写count之前的modCount值。</span><br style="line-height:25px"> 我曾经写了一篇关于happens-before的文章,有些表达可能有误,但大致还是对的,<a target="_blank" rel="nofollow" href="http://www.javaeye.com/topic/260515" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://www.javaeye.com/topic/260515</a><wbr style="line-height:25px">。<br style="line-height:25px"> 不理解的话,也只能告诉你结论了,<span style="color:#000080; line-height:25px">如果没有对count的写的话(对volatile的写是一种同步操作),读modCount可能读到很久很久很久以前的值(初始值0都有可能)</span>。<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">publicboolean</span><span style="color:#ff6600; line-height:25px">containsValue</span><span style="color:#3366ff; line-height:25px">(Objectvalue){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(value==null)<br style="line-height:25px"> thrownewNullPointerException();<br style="line-height:25px"></span><span style="color:#808080; line-height:25px">//SeeexplanationofmodCountuseabove</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">final</span><span style="color:#3366ff; line-height:25px">Segment<K,V>[]segments=this.segments;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">int[]mc=newint[segments.length];</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#808080; line-height:25px">//Tryafewtimeswithoutlocking</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(intk=0;k<RETRIES_BEFORE_LOCK;++k){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intsum=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intmcsum=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intc=segments[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.count;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">mcsum+=mc[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">=segments[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.modCount;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(segments[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.containsValue(value))</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">returntrue;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">booleancleanSweep=true;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(mcsum!=0){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#0000ff; line-height:25px"></span><span style="color:#993300; line-height:25px">int</span><span style="color:#0000ff; line-height:25px">c=segments[i]<wbr style="line-height:25px">.count;</wbr></span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(mc[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">!=segments[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.modCount){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">cleanSweep=false;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">break</span><span style="color:#3366ff; line-height:25px">;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(cleanSweep)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">returnfalse;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#808080; line-height:25px">//Resorttolockingallsegments</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">segments</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.lock();</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">booleanfound=false;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">try</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">if(segments</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.containsValue(value)){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">found=true;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">break;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><span style="color:#993300; line-height:25px">finally</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">for(inti=0;i<segments.length;++i)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">segments</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.unlock();</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">found;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">同样注意内层的第一个for循环,里面有语句</span><span style="color:#0000ff; line-height:25px">intc=segments[i]<wbr style="line-height:25px">.count</wbr></span><span style="color:#000080; line-height:25px">;但是c却从来没有被使用过,即使如此,编译器也不能做优化将这条语句去掉,因为存在对volatile变量count的读取</span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">,</span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">这条语句存在的唯一目的就是保证segments</span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">.modCount读取到几乎最新的值</span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">。</span><br style="line-height:25px"> 关于containsValue方法的其它部分就不分析了,它和size方法差不多。<br style="line-height:25px"> 跨段方法中还有一个isEmpty()方法,其实现比size()方法还要简单,也不介绍了。<br style="line-height:25px"> 最后简单地介绍下迭代方法,如keySet(),values(),entrySet()方法,这些方法都返回相应的迭代器<br style="line-height:25px"> ,所有迭代器都继承于Hash_Iterator类,里实现了主要的方法。其结构是:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">abstractclass</span><span style="color:#ff6600; line-height:25px">HashIterator</span><span style="color:#3366ff; line-height:25px">{<br style="line-height:25px"> intnextSegmentIndex;<br style="line-height:25px"> intnextTableIndex;<br style="line-height:25px"> HashEntry<K,V>[]currentTable;<br style="line-height:25px"> HashEntry<K,V>nextEntry;<br style="line-height:25px"> HashEntry<K,V>lastReturned;<br style="line-height:25px"></span><span style="color:#ff6600; line-height:25px">HashIterator</span><span style="color:#3366ff; line-height:25px">(){<br style="line-height:25px"> nextSegmentIndex=segments.length-1;<br style="line-height:25px"> nextTableIndex=-1;<br style="line-height:25px"> advance();<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">publicboolean</span><span style="color:#3366ff; line-height:25px">hasMoreElements(){returnhasNext();}<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">finalvoid</span><span style="color:#3366ff; line-height:25px">advance(){<br style="line-height:25px"> if(nextEntry!=null&&(nextEntry=nextEntry.next)!=null)<br style="line-height:25px"> return;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">while</span><span style="color:#3366ff; line-height:25px">(nextTableIndex>=0){<br style="line-height:25px"> if((nextEntry=currentTable[nextTableIndex--])!=null)<br style="line-height:25px"> return;<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">while</span><span style="color:#3366ff; line-height:25px">(nextSegmentIndex>=0){<br style="line-height:25px"> Segment<K,V>seg=segments[nextSegmentIndex--];<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(seg.count!=0){<br style="line-height:25px"> currentTable=seg.table;<br style="line-height:25px"></span><span style="color:#333300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(intj=currentTable.length-1;j>=0;--j){<br style="line-height:25px"> if((nextEntry=currentTable[j])!=null){<br style="line-height:25px"> nextTableIndex=j-1;<br style="line-height:25px"> return;<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">publicboolean</span><span style="color:#3366ff; line-height:25px"></span><span style="color:#ff6600; line-height:25px">hasNext()</span><span style="color:#3366ff; line-height:25px">{returnnextEntry!=null;}<br style="line-height:25px"> HashEntry<K,V></span><span style="color:#ff6600; line-height:25px">nextEntry</span><span style="color:#3366ff; line-height:25px">(){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(nextEntry==null)<br style="line-height:25px"> thrownewNoSuchElementException();<br style="line-height:25px"> lastReturned=nextEntry;<br style="line-height:25px"> advance();<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">lastReturned;<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">publicvoid</span><span style="color:#3366ff; line-height:25px">remove(){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(lastReturned==null)<br style="line-height:25px"> thrownewIllegalStateException();<br style="line-height:25px"> ConcurrentHashMap.this.remove(lastReturned.key);<br style="line-height:25px"> lastReturned=null;<br style="line-height:25px"> }<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="color:#000080; line-height:25px"></span><span style="color:#99cc00; line-height:25px">nextSegmentIndex</span><span style="color:#000080; line-height:25px">是段的索引,</span><span style="color:#808000; line-height:25px">nextTableIndex</span><span style="color:#000080; line-height:25px">是nextSegmentIndex对应段中中hash链的索引,<br style="line-height:25px"></span><span style="color:#339966; line-height:25px">currentTable</span><span style="color:#000080; line-height:25px">是nextSegmentIndex对应段的table。调用next方法时主要是调用了advance方法:<br style="line-height:25px"> 不想再多介绍了,唯一需要注意的是跳到下一个段时,一定要先读取下一个段的count变量。<br style="line-height:25px"> 这种迭代方式的主要效果是不会抛出ConcurrentModificationException。一旦获取到下一个段的table,<br style="line-height:25px"> 也就意味着这个段的头结点在迭代过程中就确定了,在迭代过程中就不能反映对这个段节点并发的删除和添加,<br style="line-height:25px"> 对于节点的更新是能够反映的,因为节点的值是一个</span><span style="color:#ff6600; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量。</span><br style="line-height:25px"> 结束语<br style="line-height:25px"> ConcurrentHashMap是一个支持高并发的高性能的HashMap实现,它支持完全并发的读以及一定程度并发的写。<br style="line-height:25px"> 参考文章:<br style="line-height:25px"><a target="_blank" rel="nofollow" href="http://www.ibm.com/developerworks/java/library/j-jtp08223/" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://www.ibm.com/developerworks/java/library/j-jtp08223/</a><wbr style="line-height:25px">,这个是讨论的是DougLea'sutil.concurrent包中的ConcurrentHashMap的实现,<br style="line-height:25px"> 不过大致思想是一致的。<br style="line-height:25px"><a target="_blank" rel="nofollow" href="http://floatingpoint.tinou.com/2008/09/performance-optimization-in-concurrenthashmap.html" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://floatingpoint.tinou.com/2008/09/performance-optimization-in-concurrenthashmap.html</a><wbr style="line-height:25px"><br style="line-height:25px"><a target="_blank" rel="nofollow" href="http://www.javaeye.com/topic/344876?page=3" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://www.javaeye.com/topic/344876?page=3</a></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
<wbr style="line-height:25px">实现原理</wbr><wbr style="line-height:25px"><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">锁分离(LockStripping)<br style="line-height:25px"></wbr></span><wbr style="line-height:25px"><span style="color:#003366; line-height:25px"></span><span style="color:#993300; line-height:25px">ConcurrentHashMap</span><span style="color:#003366; line-height:25px">允许多个修改操作并发进行,其关键在于使用了锁分离技术。<br style="line-height:25px"> 它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,<br style="line-height:25px"> 每个段其实就是一个小的hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。<br style="line-height:25px"> 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,<br style="line-height:25px"> 这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,<br style="line-height:25px"> 在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,<br style="line-height:25px"> 但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。<br style="line-height:25px"> 可以确保不会出现死锁,因为获得锁的顺序是固定的。不变性是多线程编程占有很重要的地位,下面还要谈到。</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注1</wbr></span><wbr style="line-height:25px">:即使数组成员也是final的,也不能解决死锁吧。<br style="line-height:25px"> 这里解决死锁注要是“按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁“来解决的。<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Thesegments,eachofwhichisaspecializedhashtable<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#0000ff; line-height:25px">finalSegment<K,V>[]segments;</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">不变(Immutable)和易变(Volatile)<br style="line-height:25px"></wbr></span><wbr style="line-height:25px"><span style="color:#993300; line-height:25px"><wbr style="line-height:25px">ConcurrentHashMap</wbr></span><span style="color:#003366; line-height:25px">完全允许多个读操作并发进行,读操作并不需要加锁</span><wbr style="line-height:25px"><span style="color:#003366; line-height:25px">。</span><br style="line-height:25px"> 如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。<br style="line-height:25px"><span style="color:#993300; line-height:25px">ConcurrentHashMap</span>实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,其结构如下所示:<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注1</wbr></span><wbr style="line-height:25px">:注意”读操作“也是要从hash链的头开始寻找的。所以在HashMap中”hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据“<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">staticfinalclass</span><span style="color:#3366ff; line-height:25px">HashEntry<K,V>{<br style="line-height:25px"> finalKkey;<br style="line-height:25px"> finalinthash;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#3366ff; line-height:25px">Vvalue;<br style="line-height:25px"> finalHashEntry<K,V>next;<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">因为这需要修改next引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">但是对于remove操作,</span><span style="color:#0000ff; line-height:25px">可<wbr style="line-height:25px">能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍</wbr></span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">,</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">将</span><span style="color:#993300; line-height:25px">value</span><span style="color:#000080; line-height:25px">设置成</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">,这</span><span style="color:#993300; line-height:25px">避免了加锁</span><span style="color:#000080; line-height:25px">。</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意1</wbr></span><wbr style="line-height:25px">:”对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍“<br style="line-height:25px"> 这操作的代价应该不下哦。这个也应该是它和HashTable比的一个劣势。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">其它</wbr></span><wbr style="line-height:25px"><br style="line-height:25px"><span style="color:#000080; line-height:25px">为了加快定位段以及段中hash槽的速度,每个段hash槽的的个数都是2^n,这使得通过位运算就可以定位段和段中hash槽的位置。<br style="line-height:25px"> 当并发级别为默认值16时,也就是段的个数,hash值的高4位决定分配在哪个段中。<br style="line-height:25px"> 但是我们也不要忘记《算法导论》给我们的教训:hash槽的的个数不应该是2^n,这可能导致hash槽分配不均,<br style="line-height:25px"> 这需要对hash值重新再hash一次。</span><br style="line-height:25px"> 这是重新hash的算法,还比较复杂,我也懒得去理解了。<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#993300; line-height:25px">privatestaticint</span><span style="color:#3366ff; line-height:25px"></span><span style="color:#ff6600; line-height:25px">hash</span><span style="color:#3366ff; line-height:25px">(inth){</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">//Spreadbitstoregularizebothsegmentandindexlocations,<br style="line-height:25px"> //usingvariantofsingle-wordWang/Jenkinshash.</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">h+=(h<<15)^0xffffcd7d;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">h^=(h>>>10);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">h+=(h<<3);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">h^=(h>>>6);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">h+=(h<<2)+(h<<14);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">h^(h>>>16);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"> 这是定位段的方法:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#0000ff; line-height:25px"></span><span style="color:#993300; line-height:25px">final</span><span style="color:#0000ff; line-height:25px">Segment<K,V></span><span style="color:#ff6600; line-height:25px">segmentFor</span><span style="color:#0000ff; line-height:25px">(inthash){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#0000ff; line-height:25px">segments[(hash>>>segmentShift)&segmentMask];<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="line-height:25px">数据结构</span><br style="line-height:25px"> 关于Hash表的基础数据结构,这里不想做过多的探讨。Hash表的一个很重要方面就是如何解决hash冲突,<br style="line-height:25px"> ConcurrentHashMap和HashMap使用相同的方式,都是将hash值相同的节点放在一个hash链中。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">与HashMap不同的是,ConcurrentHashMap使用多个子Hash表,也就是段(Segment)。</wbr></span><wbr style="line-height:25px"><br style="line-height:25px"> 下面是ConcurrentHashMap的数据成员:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#993300; line-height:25px">publicclass</span><span style="color:#3366ff; line-height:25px"></span><span style="color:#ff6600; line-height:25px">ConcurrentHashMap<K,V></span><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">extends</span><span style="color:#3366ff; line-height:25px">AbstractMap<K,V></span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">implementsConcurrentMap<K,V>,Serializable{</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Maskvalueforindexingintosegments.Theupperbitsofa<br style="line-height:25px"> *key'shashcodeareusedtochoosethesegment.<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">finalintsegmentMask;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Shiftvalueforindexingwithinsegments.<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">finalintsegmentShift;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Thesegments,eachofwhichisaspecializedhashtable<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">finalSegment<K,V>[]segments;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">所有的成员都是final的,其中segmentMask和segmentShift主要是为了定位段,参见上面的segmentFor方法。<br style="line-height:25px"> 每个Segment相当于一个子Hash表,它的数据成员如下</span>:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">staticfinalclass</span><span style="color:#3366ff; line-height:25px"></span><span style="color:#ff6600; line-height:25px">Segment<K,V></span><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">extends</span><span style="color:#3366ff; line-height:25px">ReentrantLockimplementsSerializable{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">privatestaticfinallongserialVersionUID=2249069246763182397L;</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Thenumberofelementsinthissegment'sregion.<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">transientvolatileint</span><span style="color:#3366ff; line-height:25px">count;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Numberofupdatesthatalterthesizeofthetable.Thisis<br style="line-height:25px"> *usedduringbulk-readmethodstomakesuretheyseea<br style="line-height:25px"> *consistentsnapshot:IfmodCountschangeduringatraversal<br style="line-height:25px"> *ofsegmentscomputingsizeorcheckingcontainsValue,then<br style="line-height:25px"> *wemighthaveaninconsistentviewofstateso(usually)<br style="line-height:25px"> *mustretry.<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">transientint</span><span style="color:#3366ff; line-height:25px">modCount;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Thetableisrehashedwhenitssizeexceedsthisthreshold.<br style="line-height:25px"> *(Thevalueofthisfieldisalways<tt>(int)(capacity*<br style="line-height:25px"> *loadFactor)</tt>.)<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">transientint</span><span style="color:#3366ff; line-height:25px">threshold;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Theper-segmenttable.<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">transientvolatile</span><span style="color:#3366ff; line-height:25px">HashEntry<K,V>[]table;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *Theloadfactorforthehashtable.Eventhoughthisvalue<br style="line-height:25px"> *issameforallsegments,itisreplicatedtoavoidneeding<br style="line-height:25px"> *linkstoouterobject.<br style="line-height:25px"> *@serial<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">finalfloat</span><span style="color:#3366ff; line-height:25px">loadFactor;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#000080; line-height:25px"></span><span style="color:#ff9900; line-height:25px">count</span><span style="color:#000080; line-height:25px">用来统计该段数据的个数,它是</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">,它用来协调修改和读取操作,以保证读取操作能够读取到几乎最新的修改。<br style="line-height:25px"> 协调方式是这样的,每次修改操作做了结构上的改变,如增加/删除节点(修改节点的值不算结构上的改变),都要写</span><span style="color:#0000ff; line-height:25px">count</span><span style="color:#000080; line-height:25px">值,<br style="line-height:25px"> 每次读取操作开始都要读取count的值。这利用了Java5中对volatile语义的增强,对同一个volatile变量的写和读存在happens-before关系。<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">modCount</span><span style="color:#000080; line-height:25px">统计段结构改变的次数,主要是为了检测对多个段进行遍历过程中某个段是否发生改变,在讲述跨段操作时会还会详述。<br style="line-height:25px"></span><span style="color:#ff6600; line-height:25px">threashold</span><span style="color:#000080; line-height:25px">用来表示需要进行rehash的界限值。</span><span style="color:#ff9900; line-height:25px">table</span><span style="color:#000080; line-height:25px">数组存储段中节点,每个数组元素是个</span><span style="color:#ff9900; line-height:25px">hash</span><span style="color:#000080; line-height:25px">链,用</span><span style="color:#99cc00; line-height:25px">HashEntry</span><span style="color:#000080; line-height:25px">表示。<br style="line-height:25px"> table也是</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">,这使得能够读取到最新的table值而不需要同步。</span><span style="color:#993300; line-height:25px">loadFactor</span><span style="color:#000080; line-height:25px">表示负载因子</span>。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">实现细节<br style="line-height:25px"> 修改操作<br style="line-height:25px"></wbr></span><wbr style="line-height:25px">先来看下删除操作remove(key)。<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">publicVremove(Objectkey){<br style="line-height:25px"> inthash=hash(key.hashCode());<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">segmentFor(hash).remove(key,hash,null);<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">整个操作是先定位到段,然后委托给段的remove操作。当多个删除操作并发进行时,只要它们所在的段不相同,它们就可以同时进行。</span><br style="line-height:25px"> 下面是Segment的remove方法实现:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">V</span><span style="color:#ff6600; line-height:25px">remove</span><span style="color:#3366ff; line-height:25px">(Objectkey,inthash,Objectvalue){<br style="line-height:25px"> lock();<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">try</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intc=count-1;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>[]tab=table;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intindex=hash&(tab.length-1);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>first=tab[index];</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>e=first;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">while(e!=null&&(e.hash!=hash||!key.equals(e.key)))</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">e=e.next;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">VoldValue=null;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(e!=null){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">Vv=e.value;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">if(value==null||value.equals(v)){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">oldValue=v;</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">//Allentriesfollowingremovednodecanstay<br style="line-height:25px"> //inlist,butallprecedingonesneedtobe<br style="line-height:25px"> //cloned.</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">++modCount;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>newFirst=e.next;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(HashEntry<K,V>p=first;p!=e;p=p.next)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">newFirst=newHashEntry<K,V>(p.key,p.hash,</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">newFirst,p.value);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">tab[index]=newFirst;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">count=c;</span><span style="color:#808080; line-height:25px">//write-volatile</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">oldValue;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><span style="color:#993300; line-height:25px">finally</span><span style="color:#3366ff; line-height:25px">{<br style="line-height:25px"> unlock();<br style="line-height:25px"> }<br style="line-height:25px"> }</span><br style="line-height:25px"> 整个操作是在持有段锁的情况下执行的,空白行之前的行主要是定位到要删除的节点e。<br style="line-height:25px"> 接下来,如果不存在这个节点就直接返回null,否则就要将e前面的结点复制一遍,尾结点指向e的下一个结点。<br style="line-height:25px"> e后面的结点不需要复制,它们可以重用。<br style="line-height:25px"> 注:<wbr style="line-height:25px"><span style="color:#000080; line-height:25px">删除一个节点后,它前面的节点的顺序会颠倒<wbr style="line-height:25px">。比如对于节点1,2,3,4,5。如果删除了节点4,就变成了3,2,1,5.</wbr></span><br style="line-height:25px"> 整个remove实现并不复杂,但是需要注意如下几点。<br style="line-height:25px"><span style="color:#000080; line-height:25px">第一,当要删除的结点存在时,删除的最后一步操作要将count的值减一。这必须是最后一步操作,否则读取操作可能看不到之前对段所做的结构性修改。<br style="line-height:25px"> 第二,remove执行的开始就将table赋给一个局部变量tab,这是因为table是</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量,读写</span><span style="color:#ff6600; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量的开销很大。<br style="line-height:25px"> 因为编译器也不能对volatile变量的读写做任何优化。</span><br style="line-height:25px"> 接下来看put操作,同样地put操作也是委托给段的put方法。下面是段的put方法:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">V</span><span style="color:#ff6600; line-height:25px">put</span><span style="color:#3366ff; line-height:25px">(Kkey,inthash,Vvalue,booleanonlyIfAbsent){<br style="line-height:25px"> lock();<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">try</span><span style="color:#3366ff; line-height:25px">{<br style="line-height:25px"> intc=count;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(c++>threshold)</span><span style="color:#808080; line-height:25px">//ensurecapacity</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">rehash();</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>[]tab=table;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">int</span><span style="color:#3366ff; line-height:25px">index=hash&(tab.length-1);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#0000ff; line-height:25px">HashEntry<K,V>first=tab[index];</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>e=first;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">while</span><span style="color:#3366ff; line-height:25px">(e!=null&&(e.hash!=hash||!key.equals(e.key)))</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">e=e.next;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">VoldValue;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(e!=null){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">oldValue=e.value;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(!onlyIfAbsent)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">e.value=value;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">else</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">oldValue=null;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">++modCount;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">tab[index]=newHashEntry<K,V>(key,hash,first,value);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">count=c;</span><span style="color:#808080; line-height:25px">//write-volatile</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">oldValue;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><span style="color:#993300; line-height:25px">finally</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">unlock();</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">该方法也是在持有段锁的情况下执行的,首先判断是否需要rehash,需要就先</span><span style="color:#ff6600; line-height:25px">rehash</span><span style="color:#000080; line-height:25px">。<br style="line-height:25px"> 接着是找是否存在同样一个key的结点,如果存在就直接替换这个结点的值。否则创建一个新的结点并添加到hash链的头部,<br style="line-height:25px"> 这时一定要修改modCount和count的值,同样修改count的值一定要放在最后一步。<br style="line-height:25px"> put方法调用了rehash方法,reash方法实现得也很精巧,主要利用了table的大小为2^n,这里就不介绍了。<br style="line-height:25px"> 修改操作还有putAll和replace。putAll就是多次调用put方法,没什么好说的。replace甚至不用做结构上的更改,<br style="line-height:25px"> 实现要比put和delete要简单得多,理解了put和delete,理解replace就不在话下了,这里也不介绍了。</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">获取操作<br style="line-height:25px"></wbr></span><wbr style="line-height:25px">首先看下get操作,同样ConcurrentHashMap的get操作是直接委托给Segment的get方法,直接看Segment的get方法:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">V</span><span style="color:#ff6600; line-height:25px">get</span><span style="color:#3366ff; line-height:25px">(Objectkey,inthash){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(count!=0){</span><span style="color:#808080; line-height:25px">//read-volatile</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">HashEntry<K,V>e=getFirst(hash);</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">while</span><span style="color:#3366ff; line-height:25px">(e!=null){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(e.hash==hash&&key.equals(e.key)){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">Vv=e.value;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(v!=null)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">v;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">readValueUnderLock(e);</span><span style="color:#808080; line-height:25px">//recheck</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">e=e.next;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">returnnull;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">get操作不需要锁。第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count变量,<br style="line-height:25px"> 通过这种机制保证get操作能够得到几乎最新的结构更新。对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是</span><span style="color:#ff9900; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">的,也能保证读取到最新的值。接下来就是对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。<br style="line-height:25px"> 对hash链进行遍历不需要加锁的原因在于链指针next是final的。但是头指针却不是final的,这是通过getFirst(hash)方法返回,<br style="line-height:25px"> 也就是存在table数组中的值。这使得</span><span style="color:#0000ff; line-height:25px">getFirst(hash)</span><span style="color:#000080; line-height:25px">可能返回过时的头结点,例如,当执行get方法时,刚执行完getFirst(hash)之后,<br style="line-height:25px"> 另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。这是可以允许,通过对count变量的协调机制,<br style="line-height:25px"> get能读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。<br style="line-height:25px"> 最后,如果找到了所求的结点,判断它的值如果非空就直接返回,否则在有锁的状态下再读一次。<br style="line-height:25px"> 这种情况应当很罕见,一旦发生这种情况,ConcurrentHashMap采取的方式是在持有锁的情况下再读一遍,<br style="line-height:25px"> 这能够保证读到最新的值,并且一定不会为空值。</span><br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">V</span><span style="color:#ff6600; line-height:25px">readValueUnderLock</span><span style="color:#3366ff; line-height:25px">(HashEntry<K,V>e){<br style="line-height:25px"> lock();<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">try</span><span style="color:#3366ff; line-height:25px">{<br style="line-height:25px"> returne.value;<br style="line-height:25px"> }</span><span style="color:#993300; line-height:25px">finally</span><span style="color:#3366ff; line-height:25px">{<br style="line-height:25px"> unlock();<br style="line-height:25px"> }<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="color:#000080; line-height:25px"><wbr style="line-height:25px">所求的结点的值为空,什么情况才会发生呢<wbr style="line-height:25px">?</wbr></wbr></span>且readValueUnderLock(e)能生效呢?网上有两种比较常见的说法:<br style="line-height:25px"><span style="color:#000080; line-height:25px">1)另外一条线程在修改节点<br style="line-height:25px"> 2)HashEntry构造函数中对value的赋值以及对tab[index]的赋值可能被重新排序,或许源出自<a target="_blank" rel="nofollow" href="http://www.javaeye.com/topic/344876" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://www.javaeye.com/topic/344876</a><wbr style="line-height:25px"><br style="line-height:25px"> 查看</wbr></span><span style="color:#993300; line-height:25px">ConcurrentHashMap</span><span style="color:#000080; line-height:25px">源码能看到put方法其实是创建一个新的节点并以之前的第一个节点为这个节点的next节点<br style="line-height:25px"> (即这个新创建的节点为单向链表的第一个节点),然后</span><br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#0000ff; line-height:25px">tab[index]=newHashEntry<K,V>(key,hash,first,value);</span><br style="line-height:25px"> 执行赋值<br style="line-height:25px"> remove方法则是重新把整条单向链表(除被删除Entry)全部拷贝一次执行赋值<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(HashEntry<K,V>p=first;p!=e;p=p.next)<br style="line-height:25px"> newFirst=newHashEntry<K,V>(p.key,p.hash,<br style="line-height:25px"> newFirst,p.value);<br style="line-height:25px"> tab[index]=newFirst;</span><br style="line-height:25px"> 所有,既不是另外一条线程正在修改某节点,也不是重新排序。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">最后追踪到源码的注释</wbr></span><wbr style="line-height:25px">:<br style="line-height:25px"> 引用<br style="line-height:25px"><span style="color:#808080; line-height:25px">Thisispossibleonlyifa<br style="line-height:25px"> compilerhappenstoreorderaHashEntryinitializationwith<br style="line-height:25px"> itstableassignment,whichislegalundermemorymodel<br style="line-height:25px"> butisnotknowntoeveroccur.</span><br style="line-height:25px"> 原来还是Java内存模型的问题(Jmm),执行<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#0000ff; line-height:25px">tab[index]=newFirst</span><br style="line-height:25px"> 或<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#0000ff; line-height:25px">tab[index]=newHashEntry<K,V>(key,hash,first,value);</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">时,Entry已经被认为初始化ok并返回给table(在堆中分派一块空间),这是正好有其他线程调用到get方法,<br style="line-height:25px"> 获取到这个刚“实例化好”的单向链表,但由于</span><span style="color:#ff9900; line-height:25px">JMM初始化的无序性</span><span style="color:#000080; line-height:25px">,当你get这个链表Entry中的value时,<br style="line-height:25px"> 他有可能还没完全初始化好!或按注释所说不知道发生过这样的初始化但这是合法的。。。。<br style="line-height:25px"> 这和为什么Java的<wbr style="line-height:25px">单例模式不能使用双重检查加锁的原因类似<wbr style="line-height:25px">,</wbr></wbr></span><br style="line-height:25px"> 都是由于Java内存模型(JMM)的无序性。这里有部分介绍<a target="_blank" rel="nofollow" href="http://rjx2008.javaeye.com/admin/blogs/342474" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://rjx2008.javaeye.com/admin/blogs/342474</a><wbr style="line-height:25px"><br style="line-height:25px"> 另一个操作是containsKey,这个实现就要简单得多了,因为它不需要读取值:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">boolean</span><span style="color:#ff6600; line-height:25px">containsKey</span><span style="color:#3366ff; line-height:25px">(Objectkey,inthash){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(count!=0){//read-volatile<br style="line-height:25px"> HashEntry<K,V>e=getFirst(hash);<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">while</span><span style="color:#3366ff; line-height:25px">(e!=null){<br style="line-height:25px"> if(e.hash==hash&&key.equals(e.key))<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">true;<br style="line-height:25px"> e=e.next;<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">false;<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">跨段操作<br style="line-height:25px"></wbr></span><wbr style="line-height:25px">有些操作需要涉及到多个段,比如说size(),containsValaue()。先来看下size()方法:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">publicint</span><span style="color:#3366ff; line-height:25px">size(){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">final</span><span style="color:#3366ff; line-height:25px">Segment<K,V>[]segments=this.segments;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">long</span><span style="color:#3366ff; line-height:25px">sum=0;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">long</span><span style="color:#3366ff; line-height:25px">check=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">int[]mc=newint[segments.length];</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">//Tryafewtimestogetaccuratecount.Onfailuredueto<br style="line-height:25px"> //continuousasyncchangesintable,resorttolocking.</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(intk=0;k<RETRIES_BEFORE_LOCK;++k){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">check=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">sum=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intmcsum=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">sum+=segments[i]</span><span style="color:#3366ff; line-height:25px">.count;</span><br style="line-height:25px"><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px">mcsum+=mc[i]<wbr style="line-height:25px">=segments[i]</wbr></span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.modCount;<br style="line-height:25px"> }<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(mcsum!=0){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i){<br style="line-height:25px"> check+=segments[i]</span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.count;<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(mc[i]</span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">!=segments[i]</wbr></span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.modCount){<br style="line-height:25px"> check=-1;//forceretry<br style="line-height:25px"> break;<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> if(check==sum)<br style="line-height:25px"> break;<br style="line-height:25px"> }<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(check!=sum){</span><span style="color:#808080; line-height:25px">//Resorttolockingallsegments</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">sum=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i)<br style="line-height:25px"> segments</span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.lock();<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i)<br style="line-height:25px"> sum+=segments</span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.count;<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i)<br style="line-height:25px"> segments</span></span><span style="line-height:25px; font-style:normal"><span style="color:#3366ff; line-height:25px"><wbr style="line-height:25px">.unlock();<br style="line-height:25px"> }<br style="line-height:25px"></wbr></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(sum>Integer.MAX_VALUE)<br style="line-height:25px"> returnInteger.MAX_VALUE;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">else</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">(int)sum;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"></span><br style="line-height:25px"><span style="color:#000080; line-height:25px"><wbr style="line-height:25px">size方法主要思路是先在没有锁的情况下对所有段大小求和</wbr></span><wbr style="line-height:25px"><span style="color:#003366; line-height:25px">,如果不能成功</span><br style="line-height:25px"><span style="line-height:25px; color:rgb(0,51,102)"></span><span style="color:#003366; line-height:25px">(这是因为遍历过程中可能有其它线程正在对已经遍历过的段进行结构性更新),</span><br style="line-height:25px"><span style="color:#003366; line-height:25px">最多执行RETRIES_BEFORE_LOCK次,如果还不成功就在持有所有段锁的情况下再对所有段大小求和。</span><br style="line-height:25px"><span style="color:#003366; line-height:25px">在没有锁的情况下主要是利用Segment中的modCount进行检测,在遍历过程中保存每个Segment的modCount,</span><br style="line-height:25px"><span style="color:#003366; line-height:25px">遍历完成之后再检测每个Segment的modCount有没有改变,如果有改变表示有其它线程正在对Segment进行结构性并发更新,需要重新计算。</span><br style="line-height:25px"><span style="color:#003366; line-height:25px">其实</span>这种方式是存在问题的,在第一个内层for循环中,在这两条语句sum+=segments<wbr style="line-height:25px">.count;mcsum+=mc<wbr style="line-height:25px">=segments<wbr style="line-height:25px">.modCount;之间,<br style="line-height:25px"> 其它线程可能正在对Segment进行结构性的修改,导致segments<wbr style="line-height:25px">.count和segments<wbr style="line-height:25px">.modCount读取的数据并不一致。<br style="line-height:25px"> 这可能使size()方法返回任何时候都不曾存在的大小,很奇怪javadoc居然没有明确标出这一点,可能是因为这个时间窗口太小了吧。<br style="line-height:25px"><span style="color:#000080; line-height:25px"><wbr style="line-height:25px">size()的实现还有一点需要注意,必须要先segments<wbr style="line-height:25px">.count,才能segments<wbr style="line-height:25px">.modCount,这是因为segment<wbr style="line-height:25px">.count是对volatile变量的访问,<br style="line-height:25px"> 接下来segments<wbr style="line-height:25px">.modCount才能得到几乎最新的值</wbr></wbr></wbr></wbr></wbr></span><wbr style="line-height:25px">(前面我已经说了为什么只是“几乎”了)。<br style="line-height:25px"> 这点在containsValue方法中得到了淋漓尽致的展现:<br style="line-height:25px"> 关于size()的实现"必须要先segments<wbr style="line-height:25px">.count,才能segments<wbr style="line-height:25px">.modCount"的原因:<br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">写</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量和它之前的读写操作是不能</span><span style="color:#ff6600; line-height:25px">reorde</span><span style="color:#000080; line-height:25px">r的,读</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量和它之后的读写操作也是不能reorder的。<br style="line-height:25px"> 修改modCount发生在修改count之前,由于count是</span><span style="color:#993300; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量,修改modCount不能和写count的操作reorder,读取count和它之后的操作,</span><span style="color:#000080; line-height:25px">比如读取modCount,不能reorder。有了这两个不能reorder才能保证读取了count之后,能读到线程在写count之前的写入的modCount值,这个modCount值是几乎最新的。如果在读</span><span style="color:#99cc00; line-height:25px">modCount</span><span style="color:#000080; line-height:25px">之前不读count,读modCount甚至可能会reorder到写modCount之前。<br style="line-height:25px"> 用reorder解释总是太复杂了,不如用</span><span style="color:#ff6600; line-height:25px">happens-before</span><span style="color:#000080; line-height:25px">来得简洁。当一个线程I对count的读时,它读到的值必定是另一个线程,<br style="line-height:25px"> 假设是线程II,最近对count的写。这个两个操作存在happens-before关系,即线程II对count的写happens-before线程I对count的读,<br style="line-height:25px"> 记作:II:W(count)<I:R(count)。单线程的happens-before规则,又有II:W(modCount)<II:W(count)(查看源代码会发现在写count之前必定有写modCount),<br style="line-height:25px"> 以及I:R(count)<I:R(modCount),根据传递规则有,II:W(modCount)<I:R(modCount),这就是说线程I至少能够读取到线程II写count之前的modCount值。</span><br style="line-height:25px"> 我曾经写了一篇关于happens-before的文章,有些表达可能有误,但大致还是对的,<a target="_blank" rel="nofollow" href="http://www.javaeye.com/topic/260515" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://www.javaeye.com/topic/260515</a><wbr style="line-height:25px">。<br style="line-height:25px"> 不理解的话,也只能告诉你结论了,<span style="color:#000080; line-height:25px">如果没有对count的写的话(对volatile的写是一种同步操作),读modCount可能读到很久很久很久以前的值(初始值0都有可能)</span>。<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">publicboolean</span><span style="color:#ff6600; line-height:25px">containsValue</span><span style="color:#3366ff; line-height:25px">(Objectvalue){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(value==null)<br style="line-height:25px"> thrownewNullPointerException();<br style="line-height:25px"></span><span style="color:#808080; line-height:25px">//SeeexplanationofmodCountuseabove</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">final</span><span style="color:#3366ff; line-height:25px">Segment<K,V>[]segments=this.segments;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">int[]mc=newint[segments.length];</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#808080; line-height:25px">//Tryafewtimeswithoutlocking</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(intk=0;k<RETRIES_BEFORE_LOCK;++k){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intsum=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intmcsum=0;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">intc=segments[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.count;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">mcsum+=mc[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">=segments[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.modCount;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(segments[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.containsValue(value))</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">returntrue;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">booleancleanSweep=true;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(mcsum!=0){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#0000ff; line-height:25px"></span><span style="color:#993300; line-height:25px">int</span><span style="color:#0000ff; line-height:25px">c=segments[i]<wbr style="line-height:25px">.count;</wbr></span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(mc[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">!=segments[i]</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.modCount){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">cleanSweep=false;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">break</span><span style="color:#3366ff; line-height:25px">;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(cleanSweep)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">returnfalse;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#808080; line-height:25px">//Resorttolockingallsegments</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">segments</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.lock();</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">booleanfound=false;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">try</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(inti=0;i<segments.length;++i){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">if(segments</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.containsValue(value)){</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">found=true;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">break;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><span style="color:#993300; line-height:25px">finally</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">for(inti=0;i<segments.length;++i)</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">segments</span><wbr style="line-height:25px"><span style="color:#3366ff; line-height:25px">.unlock();</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">found;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">同样注意内层的第一个for循环,里面有语句</span><span style="color:#0000ff; line-height:25px">intc=segments[i]<wbr style="line-height:25px">.count</wbr></span><span style="color:#000080; line-height:25px">;但是c却从来没有被使用过,即使如此,编译器也不能做优化将这条语句去掉,因为存在对volatile变量count的读取</span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">,</span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">这条语句存在的唯一目的就是保证segments</span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">.modCount读取到几乎最新的值</span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">。</span><br style="line-height:25px"> 关于containsValue方法的其它部分就不分析了,它和size方法差不多。<br style="line-height:25px"> 跨段方法中还有一个isEmpty()方法,其实现比size()方法还要简单,也不介绍了。<br style="line-height:25px"> 最后简单地介绍下迭代方法,如keySet(),values(),entrySet()方法,这些方法都返回相应的迭代器<br style="line-height:25px"> ,所有迭代器都继承于Hash_Iterator类,里实现了主要的方法。其结构是:<br style="line-height:25px"> Java代码<br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">abstractclass</span><span style="color:#ff6600; line-height:25px">HashIterator</span><span style="color:#3366ff; line-height:25px">{<br style="line-height:25px"> intnextSegmentIndex;<br style="line-height:25px"> intnextTableIndex;<br style="line-height:25px"> HashEntry<K,V>[]currentTable;<br style="line-height:25px"> HashEntry<K,V>nextEntry;<br style="line-height:25px"> HashEntry<K,V>lastReturned;<br style="line-height:25px"></span><span style="color:#ff6600; line-height:25px">HashIterator</span><span style="color:#3366ff; line-height:25px">(){<br style="line-height:25px"> nextSegmentIndex=segments.length-1;<br style="line-height:25px"> nextTableIndex=-1;<br style="line-height:25px"> advance();<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">publicboolean</span><span style="color:#3366ff; line-height:25px">hasMoreElements(){returnhasNext();}<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">finalvoid</span><span style="color:#3366ff; line-height:25px">advance(){<br style="line-height:25px"> if(nextEntry!=null&&(nextEntry=nextEntry.next)!=null)<br style="line-height:25px"> return;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">while</span><span style="color:#3366ff; line-height:25px">(nextTableIndex>=0){<br style="line-height:25px"> if((nextEntry=currentTable[nextTableIndex--])!=null)<br style="line-height:25px"> return;<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">while</span><span style="color:#3366ff; line-height:25px">(nextSegmentIndex>=0){<br style="line-height:25px"> Segment<K,V>seg=segments[nextSegmentIndex--];<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(seg.count!=0){<br style="line-height:25px"> currentTable=seg.table;<br style="line-height:25px"></span><span style="color:#333300; line-height:25px">for</span><span style="color:#3366ff; line-height:25px">(intj=currentTable.length-1;j>=0;--j){<br style="line-height:25px"> if((nextEntry=currentTable[j])!=null){<br style="line-height:25px"> nextTableIndex=j-1;<br style="line-height:25px"> return;<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">publicboolean</span><span style="color:#3366ff; line-height:25px"></span><span style="color:#ff6600; line-height:25px">hasNext()</span><span style="color:#3366ff; line-height:25px">{returnnextEntry!=null;}<br style="line-height:25px"> HashEntry<K,V></span><span style="color:#ff6600; line-height:25px">nextEntry</span><span style="color:#3366ff; line-height:25px">(){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(nextEntry==null)<br style="line-height:25px"> thrownewNoSuchElementException();<br style="line-height:25px"> lastReturned=nextEntry;<br style="line-height:25px"> advance();<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">lastReturned;<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">publicvoid</span><span style="color:#3366ff; line-height:25px">remove(){<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">if</span><span style="color:#3366ff; line-height:25px">(lastReturned==null)<br style="line-height:25px"> thrownewIllegalStateException();<br style="line-height:25px"> ConcurrentHashMap.this.remove(lastReturned.key);<br style="line-height:25px"> lastReturned=null;<br style="line-height:25px"> }<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="color:#000080; line-height:25px"></span><span style="color:#99cc00; line-height:25px">nextSegmentIndex</span><span style="color:#000080; line-height:25px">是段的索引,</span><span style="color:#808000; line-height:25px">nextTableIndex</span><span style="color:#000080; line-height:25px">是nextSegmentIndex对应段中中hash链的索引,<br style="line-height:25px"></span><span style="color:#339966; line-height:25px">currentTable</span><span style="color:#000080; line-height:25px">是nextSegmentIndex对应段的table。调用next方法时主要是调用了advance方法:<br style="line-height:25px"> 不想再多介绍了,唯一需要注意的是跳到下一个段时,一定要先读取下一个段的count变量。<br style="line-height:25px"> 这种迭代方式的主要效果是不会抛出ConcurrentModificationException。一旦获取到下一个段的table,<br style="line-height:25px"> 也就意味着这个段的头结点在迭代过程中就确定了,在迭代过程中就不能反映对这个段节点并发的删除和添加,<br style="line-height:25px"> 对于节点的更新是能够反映的,因为节点的值是一个</span><span style="color:#ff6600; line-height:25px">volatile</span><span style="color:#000080; line-height:25px">变量。</span><br style="line-height:25px"> 结束语<br style="line-height:25px"> ConcurrentHashMap是一个支持高并发的高性能的HashMap实现,它支持完全并发的读以及一定程度并发的写。<br style="line-height:25px"> 参考文章:<br style="line-height:25px"><a target="_blank" rel="nofollow" href="http://www.ibm.com/developerworks/java/library/j-jtp08223/" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://www.ibm.com/developerworks/java/library/j-jtp08223/</a><wbr style="line-height:25px">,这个是讨论的是DougLea'sutil.concurrent包中的ConcurrentHashMap的实现,<br style="line-height:25px"> 不过大致思想是一致的。<br style="line-height:25px"><a target="_blank" rel="nofollow" href="http://floatingpoint.tinou.com/2008/09/performance-optimization-in-concurrenthashmap.html" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://floatingpoint.tinou.com/2008/09/performance-optimization-in-concurrenthashmap.html</a><wbr style="line-height:25px"><br style="line-height:25px"><a target="_blank" rel="nofollow" href="http://www.javaeye.com/topic/344876?page=3" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://www.javaeye.com/topic/344876?page=3</a></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>