ITPub博客

首页 > 应用开发 > Java > Java并发之ConcurrentHashMap

Java并发之ConcurrentHashMap

原创 Java 作者:541732025 时间:2014-04-01 13:31:46 0 删除 编辑

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/,如需转载,请注明出处,否则将追究法律责任。

上一篇: Java锁的优化
请登录后发表评论 登录
全部评论

注册时间:2013-05-23

  • 博文量
    127
  • 访问量
    482900