ConcurrentHashMap,线程安全的HashMap,由于HashTable较重量级,他会给整个加锁,而ConcurrentHashMap只是给每个Segment加锁,所以性能快很多。
除了initialCapacity、loadFactor之外,还有一个concurrentLevel属性,默认情况下,三个属性分别为16,0.75,16
设置以上三个属性后,就得考虑锁加在哪?并怎样初始化加锁的对象?
点击(此处)折叠或打开
- int sshift = 0;
- int ssize = 1;
- while(ssize < concurrentLevel){
- ++sshift;
- ssize <<= 1;
- }
上面这段代码意思是:计算出一个不小于concurrentLevel的ssize值,而且它是2的n次方。
默认情况下,ssize为16,根据这个参数传入Segment的newArray方法,创建大小为16的Segment数组
创建Segment数组后,数组元素对象怎么初始化?
点击(此处)折叠或打开
- int c = initialCapacity /ssize
- if(c* ssize < initialCapacity){
- ++c;
- }
- int cap = 1;
- while(cap < c){
- cap << 1;
- }
上面代码意思是:用Map容量除以Segment数组大小,看每个Segment需要初始化多大,这里16/16=1,所以创建大小为cap=1的HashEntry[]数组,将其赋给Segment,并且基于cap值和loadFactor计算threshold值。Segment继承自ReentrantLock。可以发现。一个Segment的数据结构就相当于HashMap(数组下有链表)
点击(此处)折叠或打开
- threshold = (int)(newTable.length * loadFactor)
put(key,value)
ConcurrentHashMap并没有对整个方法加锁(而HashTable对整个加锁),和HashMap一样,首先对key.hashCode进行hash操作,得到hash值后计算其对应在数组中的哪个Segment对象。
点击(此处)折叠或打开
- return segments[(hash >>> segmentShift) & segmentMask]
找到数组中的Segment对象后,接着调用Segment的put方法完成操作,至此,才对其进行加锁:lock,接着判断当前存储的对象个数加1后是否大于threshold,如大于,则rehash,将当前HashEntry[]数组扩大2倍,并重hash对象。
其余的操作跟HashMap差不多,有则覆盖,没有则新创建HashEntry对象,放在链表头部。
点击(此处)折叠或打开
- HashEntry<K,V>[] newTable = HashEntry.newArray(oldCapacity<<1)
get(key)
get操作只有在e.value == null的情况下,才会加lock再执行一次e.value
问题:get操作大部分情况没有lock,它是怎样保证并发下数据的一致性的呢?
譬如1:在get找HashEntry链表过程中,这时候可能HashEntry[]数组会发生改变(put操作执行),那它是如何让保证的呢?
答案就是因为HashEntry[]数组是volatile的,当put改变数组后,get操作会立刻得到更新。并且,jdk5以后,volatile语义增强了,不仅仅保证数据的可见性,还能保证禁止在对象上的读写重排序,所以,在get时读取到的HashEntry[]是最新的、并且构造已经完全的
譬如2:当get操作已经找到了HashEntry,准备开始遍历链表了,这时HashEntry发生变化了怎么办?
答案就是HashEntry对象中的hash、key、next属性都是final的,这就意味着不能插入一个新的HashEntry在所遍历的任何HashEntry的next下,这样就可以保证当获取到HashEntry对象后,其基于next属性构建的链表是不会发生变化的。
还有一个问题,为什么要判断e.value是否为null?而且如果为null再调用readValueUnderLock(HashEntry e)?
以下为readValueUnderLock方法:
点击(此处)折叠或打开
- /**
- * Reads value field of an entry under lock. Called if value
- * field ever appears to be null. This is possible only if a
- * compiler happens to reorder a HashEntry initialization with
- * its table assignment, which is legal under memory model
- * but is not known to ever occur.
- */
- V readValueUnderLock(HashEntry<K,V> e) {
- lock();
- try {
- return e.value;
- } finally {
- unlock();
- }
- }
通过它的注释,我们明白了,This is possible only if a compiler happens to reorder a HashEntry initialization with its table assignment,意思就是,只有在HashEntry初始化时出现指令重排,才会导致该方法调用,并且也不确定是否发生。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/28912557/viewspace-1133899/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/28912557/viewspace-1133899/