ConcurrentHashMap原理解析,jdk1.7与1.8区别

总所周知,HashMap是非线程安全的工具类,在多线程环境下就需要一个线程安全的工具类来帮助我们实现线程安全,jdk1.5之前,我们使用HashTable,HashTable只是用synchronized同步关键字将put等大多数方法进行加锁,锁定的是整个对象,因此效率不高,jdk1.5之后,ConcurrentHashMap随着j.u.c包一起引入了JDK中,相对于HashTable,ConcurrentHashMap性能有了大幅提升,成为了线程安全的HashMap首选方案。

jdk1.7----分段式锁Segment

jdk1.7以及之前的版本,ConcurrentHashMap采用分段式锁,相比HashTable锁住整个对象,分段式锁的优点在于对整个对象划分成多个分段,操作数据时锁定的只是数据所在的分段。

存储结构
ConcurrentHashMap原理解析,jdk1.7与1.8区别_第1张图片
ConcurrentHashMap底层使用一个Segment数组作为存储结构,而Segment继承ReentrantLock,其实是一把锁
ConcurrentHashMap原理解析,jdk1.7与1.8区别_第2张图片
Segment内部还包含一个HashEntry数组,也就是说,Segment还实现了HashMap的功能(如果不了解HashMap可以看我之前的HashMap原理)
ConcurrentHashMap原理解析,jdk1.7与1.8区别_第3张图片
由此我们可以看出ConcurrentHashMap的存储结构是这样的,每个Segment即是一把锁,又是一个HashMap,类似于对HashMap上面做了一层封装,变成了一个HashMap数组,每个HashMap拥有自己的锁
ConcurrentHashMap原理解析,jdk1.7与1.8区别_第4张图片
初始化
ConcurrentHashMap默认构造函数相对于HashMap多了一个并发级别参数,值为16,
ConcurrentHashMap原理解析,jdk1.7与1.8区别_第5张图片
调用了带有initialCapacity,float loadFactor, int concurrencyLevel的构造函数,第一个while循环中,ssize被赋值为大于等于并发级别的最小2的幂次方数,这里还是16。ssize是Segment数组的长度,所以Segment数组的长度依赖于concurrencyLevel参数(这是理所当然的,Segment是一把锁,锁越多,并发级别越高)。initialCapacity,float loadFactor用于初始化Segment,原理与HashMap差不多。

public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        this.segmentShift = 32 - sshift;
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
        Segment s0 =
            new Segment(loadFactor, (int)(cap * loadFactor),
                             (HashEntry[])new HashEntry[cap]);
        Segment[] ss = (Segment[])new Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

put操作
ConcurrentHashMap原理解析,jdk1.7与1.8区别_第6张图片
在put方法中,先根据key计算哈希值,再计算segment数组的index,得到一个Segment对象,然后调用Segment的put方法
ConcurrentHashMap原理解析,jdk1.7与1.8区别_第7张图片
Segment的put方法中,会先进行加锁,也就是锁最终加锁的地方在Segment,如果访问的是不同的Segment,就不会有锁的争用,提高了并发性能。

get操作
get方法比较简单,用UNSAFE.getObjectVolatile获取属性,该方法获取对象中offset偏移地址对应的field的值,支持volatile load语义
ConcurrentHashMap原理解析,jdk1.7与1.8区别_第8张图片

jdk1.8----取消Segment概念

jdk1.8中ConcurrentHashMap取消了分段式锁的封装,存储结构和数据操作原理与普通HashMap一样,数组+[链表|树]。其保证线程安全的核心思想是,插入元素时如果没有冲突,也就是说index处为空,则执行CAS插入操作;如果index处已经有元素了,则使用synchronized锁定index处元素,再进行插入操作。
ConcurrentHashMap原理解析,jdk1.7与1.8区别_第9张图片
对于get操作使用的也是getObjectVolatile方法
ConcurrentHashMap原理解析,jdk1.7与1.8区别_第10张图片
在这里插入图片描述

你可能感兴趣的:(Java并发编程)