浅析ConcurrentHashMap
这个类要从HashMap讲起,HashMap是一个简单的hash表,稍微详细一点的介绍可以在网上查一下资料,或者看我以前写的《java集合总结》。这个集合有一个缺点是线程不安全,也就是说,如果在多线程下同时读写可能会产生意想不到的结果。比如这样:
他可能就会输出这样的结果:
这是为什么?
导致HashMap线程不安全的根本原因是扩容。扩容就是在put加入元素的个数超过capacity * loadFactor的时候就会将内部Entry数组大小扩大至原来的2倍,然后将数组元素按照新的数组大小重新计算索引,放在新的数组中,同时修改每个节点的链表关系(主要是next和节点在链表中的位置)。在扩容的过程中,每个线程会申请扩容后大小的新数组,移动完成后将新数组复制到Entry数组中。如果多个线程同时进行扩容,由于线程的执行顺序的不确定性,导致多个线程操作节点的next值会发生不确定的变化,造成数据丢失,或者形成循环链表等问题,导致线程不安全。
这时我们急需一个线程安全的hashMap,HashTable刚好满足,如果上述代码中的HashMap改成HashTable,那么就不会再有问题。
整个put都被synchronized包裹了,包括真正的addEntry里面涉及到扩容的代码,确实这样是解决了问题,但是是不是太粗糙了?想想在扩容的时候整段代码被锁住了,只有一个线程能访问,那是不是效率特别低?
幸好聪明地人们还是想出了解决办法:ConcurrentHashMap
|
|
这一段是ConcurrenthashMap的put代码,其中
其实是对key的hashCode再做了一次分割,可以想象成一个二维数组,key的hashCode决定了第一维数组的下标,key的hashCode的spread值决定了二维数组的值,然后后面是针对不同场景的操作,比如空的时候直接插入,非空的时候对这某一个纬度加一个锁,确保影响的范围变小,这样就达到了既线程安全,效率又有一个明显的提升的效果,其中还有很多细节等待挖掘。