Java并发之ConcurrentHashMap

ConcurrentHashMap,线程安全的HashMap,由于HashTable较重量级,他会给整个加锁,而ConcurrentHashMap只是给每个Segment加锁,所以性能快很多。
除了initialCapacity、loadFactor之外,还有一个concurrentLevel属性,默认情况下,三个属性分别为16,0.75,16
设置以上三个属性后,就得考虑锁加在哪?并怎样初始化加锁的对象?

点击(此处)折叠或打开

  1. int sshift = 0;
  2. int ssize = 1;
  3. while(ssize < concurrentLevel){
  4.     ++sshift;
  5.     ssize <<= 1;
  6. }

上面这段代码意思是:计算出一个不小于concurrentLevel的ssize值,而且它是2的n次方。
默认情况下,ssize为16,根据这个参数传入Segment的newArray方法,创建大小为16的Segment数组
创建Segment数组后,数组元素对象怎么初始化?

点击(此处)折叠或打开

  1. int c = initialCapacity /ssize
  2. if(c* ssize < initialCapacity){
  3.     ++c;
  4. }
  5. int cap = 1;
  6. while(cap < c){
  7.     cap << 1;
  8. }

上面代码意思是:用Map容量除以Segment数组大小,看每个Segment需要初始化多大,这里16/16=1,所以创建大小为cap=1的HashEntry[]数组,将其赋给Segment,并且基于cap值和loadFactor计算threshold值。Segment继承自ReentrantLock。可以发现。一个Segment的数据结构就相当于HashMap(数组下有链表

点击(此处)折叠或打开

  1. threshold = (int)(newTable.length * loadFactor)


put(key,value)
ConcurrentHashMap并没有对整个方法加锁(而HashTable对整个加锁),和HashMap一样,首先对key.hashCode进行hash操作,得到hash值后计算其对应在数组中的哪个Segment对象。

点击(此处)折叠或打开

  1. return segments[(hash >>> segmentShift) & segmentMask]

找到数组中的Segment对象后,接着调用Segment的put方法完成操作,至此,才对其进行加锁:lock,接着判断当前存储的对象个数加1后是否大于threshold,如大于,则rehash,将当前HashEntry[]数组扩大2倍,并重hash对象。
其余的操作跟HashMap差不多,有则覆盖,没有则新创建HashEntry对象,放在链表头部。

点击(此处)折叠或打开

  1. 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方法:

点击(此处)折叠或打开

  1. /**
  2.          * Reads value field of an entry under lock. Called if value
  3.          * field ever appears to be null. This is possible only if a
  4.          * compiler happens to reorder a HashEntry initialization with
  5.          * its table assignment, which is legal under memory model
  6.          * but is not known to ever occur.
  7.          */
  8.         V readValueUnderLock(HashEntry<K,V> e) {
  9.             lock();
  10.             try {
  11.                 return e.value;
  12.             } finally {
  13.                 unlock();
  14.             }
  15.         }



通过它的注释,我们明白了,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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值