美文网首页
Java并发之ConcurrentHashMap(JDK1.6)

Java并发之ConcurrentHashMap(JDK1.6)

作者: 田真的架构人生 | 来源:发表于2017-08-10 09:53 被阅读0次

    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[] 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的,value是volatile的,这就意味着已获取的HashEntry不会有next加入进来,而且value是可见的。
    还有一个问题,为什么要判断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 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初始化时出现指令重排,才会导致该方法调用,并且也不确定是否发生。

    所以说,在JDK1.6里面,是弱一致性的,因为所有可见性都是以count实现的,当put和get并发时,get可能获取不到最新的结果。而在1.7里面,会有UNSAFE.getObjectVolatile保证。

    相关文章

      网友评论

          本文标题:Java并发之ConcurrentHashMap(JDK1.6)

          本文链接:https://www.haomeiwen.com/subject/chncrxtx.html