ConcurrentHashMap源码解读

数据结构


源码中的声明

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable {
		//底层就是一个Segment数组
		final Segment<K,V>[] segments;

		//初始化时候Segment数组的默认长度
		static final int DEFAULT_INITIAL_CAPACITY = 16;

		//加载因子
		static final float DEFAULT_LOAD_FACTOR = 0.75f;

		//默认的线程并发级别,就是并发数,默认16个线程进行并发
		static final int DEFAULT_CONCURRENCY_LEVEL = 16;

		//最大容量entryies的个数
		static final int MAXIMUM_CAPACITY = 1 << 30;

		//Segment数组的最小容量
		static final int MIN_SEGMENT_TABLE_CAPACITY = 2;

		//最大的Segament的个数
		static final int MAX_SEGMENTS = 1 << 16; // slightly conservative

ConcurrentHashMap就是一个Segment数组,每个Segment里面存储的是一个哈希表。

static final class Segment<K,V> extends ReentrantLock implements Serializable {
    // Hash table,默认初始容量为2,特别注意,这里table用了volatile修饰,
    // 保证多线程读写时的可见性
    transient volatile HashEntry<K,V>[] table;
    // segment中hashtable的元素数量计数器,用于size方法中,分段计算汇总
    transient int count;
    // 执行更新操作时,获取segment锁的重试次数,多核CPU重试64次,单核CPU重试1次
    static final int MAX_SCAN_RETRIES =
            Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
    // 其他省略
}

Segment继承ReentrantLock,每个Segment元素都一个锁,实现了分段锁,锁的粒度更细化,支持更高的并发。

ConcurrentHashMap初始化

 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;//用来定义Segment[]的长度
		//ssize的长度是大于并发级别数的最小2次方
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        this.segmentShift = 32 - sshift;
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
		//定义每个Segment元素里面哈希表的长度
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;//默认哈希表的长度是2
        //cap是每个Segment元素哈希表的长度,最后也是2的n次方
		while (cap < c)
            cap <<= 1;
        
		//创建Segment[0]和Segment数组
        Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                             (HashEntry<K,V>[])new HashEntry[cap]);
        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
		// UNSAFE为sun.misc.Unsafe对象,使用CAS操作,
        // 将segments[0]的元素替换为已经初始化的s0,保证原子性。
        // Unsafe类采用C++语言实现,底层实现CPU的CAS指令操作,保证原子性。
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

从构造方法看出,根据并发数计算Segment数组的长度ssize和每个HashEntry数组的长度cap,并初始化Segment[0].

ssize的长度是2的n次方,并且默认长度为16,每个hashEntry数组的长度也是2的n次方,最小为2

put方法实现

 public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j);
        return s.put(key, hash, value, false);
    }
	private int hash(Object k) {
        int h = hashSeed;

        if ((0 != h) && (k instanceof String)) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // Spread bits to regularize both segment and index locations,
        // using variant of single-word Wang/Jenkins hash.
        h += (h <<  15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h <<   3);
        h ^= (h >>>  6);
        h += (h <<   2) + (h << 14);
        return h ^ (h >>> 16);
    }

1. 根据key计算出hash值,由此得到Segment元素的下标位置

2. 检查下标segment是否已经初始化,如果没有初始化,则调用ensureSegment进行初始化,内部用了CAS操作进行替换,达到初始化效果。初始化的过程进行了双重检查,UNSAFE.getObjectVolatile,通过这个方法执行了两次,以检查segment是否已经初始化,以及用UNSAFE.compareAndSwapObject进行CAS替换,CAS的替换有失败的可能,因此源码中还加了自旋重试的操作,保证最终CAS操作的成功。

private Segment<K,V> ensureSegment(int k) {
        final Segment<K,V>[] ss = this.segments;
        long u = (k << SSHIFT) + SBASE; // raw offset
        Segment<K,V> seg;
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
            Segment<K,V> proto = ss[0]; // use segment 0 as prototype
            int cap = proto.table.length;
            float lf = proto.loadFactor;
            int threshold = (int)(cap * lf);
            HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
            if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                == null) { // recheck
                Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
                while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                       == null) {
                    if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                        break;
                }
            }
        }
        return seg;
    }

3. 调用Segment的put方法,将元素放到HashEntry数组中,过程中会去获取锁

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

当有线程A和线程B在相同segment对象上put对象时,执行过程如下:

1、线程A执行tryLock()方法获取锁。
2、线程B获取锁失败,则执行scanAndLockForPut()方法,在scanAndLockForPut方法中,会通过重复执行tryLock()方法尝试获取锁,
   在多处理器环境下,重复次数为64,单处理器重复次数为1,当执行tryLock()方法的次数超过上限时,则执行lock()方法挂起线程B;
   这样设计目的是为了让线程切换和自旋消耗的CPU的时间达到平衡,不至于白白浪费CPU,也不会过于平凡切换线程导致更多的CPU浪费。
3、获得锁之后,根据hash值定位到HashEntry数组的下标,更新或插入元素,在插入过程中,如果HashEntry数组元素个数容量超过负载比例,
   则进行rehash操作扩容,扩容为原来的两倍(rehash请对比Java集合-HashMap源码实现深入解析中的逻辑,自行分析,基本一模一样);
4、在插入后,还会更新segment的count计数器,用于size方法中计算map元素个数时不用对每个segment内部HashEntry遍历重新计算,提高性能。
5、当线程A执行完插入操作后,会通过unlock()方法释放锁,接着唤醒线程B继续执行;

get方法

get方法是不回去获取锁的,根据key计算hash值,定位到Segment元素位置,并且使用UNSAFE的get方法保证获取的元素是最新的。

public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }
size方法

public int size() {
        // Try a few times to get accurate count. On failure due to
        // continuous async changes in table, resort to locking.
        final Segment<K,V>[] segments = this.segments;
        int size;
        boolean overflow; // true if size overflows 32 bits
        long sum;         // sum of modCounts
        long last = 0L;   // previous sum
        int retries = -1; // first iteration isn't retry
        try {
            for (;;) {
			    //尝试RETRIES_BEFORE_LOCK次后还无法统计到正确的大小,就将
				//整个Segment数组锁住,进行hashEntry数组的长度累计
                if (retries++ == RETRIES_BEFORE_LOCK) {
                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation
                }
                sum = 0L;
                size = 0;
                overflow = false;
				//两次查询modCount,判断数据结构是否没有变化,如果没有变化,则是跳出循环,返回size
                for (int j = 0; j < segments.length; ++j) {
                    Segment<K,V> seg = segmentAt(segments, j);
                    if (seg != null) {
                        sum += seg.modCount;
                        int c = seg.count;
                        if (c < 0 || (size += c) < 0)
                            overflow = true;
                    }
                }
                if (sum == last)
                    break;
                last = sum;
            }
        } finally {
            if (retries > RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    segmentAt(segments, j).unlock();
            }
        }
        return overflow ? Integer.MAX_VALUE : size;
    }

1. 重复计算Segment的modCount 和 hashEntry数组的size大小,并汇总

2. 前一次的sum和后一次的last(分别表示两次的modCount汇总)相等,表示数据结构没有变化,就返回累计的size

3. 否则,就再次检查sum和last,尝试RETRIES_BEFORE_LOCK次还是无法统计到正确的值,就将整个Segment数组都锁住,累计size大小,并在finally中释放锁

注意,累计的size大小是大概的值,比如说如果在last==sum情况下,跳出循环,再返回size之前,存在一个线程put元素,返回的值就会有问题

转载:https://www.jianshu.com/p/47c1be88a88e

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值