JAVA并发包(二十):ConcurrentHashMap

本文详细解析了ConcurrentHashMap在JDK7与JDK8中的实现差异,包括数据结构、线程安全机制及核心方法的分析,如put、get、size等,帮助读者深入理解高并发环境下高效Map的选择。

ConcurrentHashMap也是并发环境中常见的Map,如果在高并发中没有排序等特别的需要,我们可以优先选择ConcurrenHashMap存储key-value键值对。

ConcurrentHashMap一般有两个版本的实现,jdk7(包括7)之前是Segment数组+Hash桶的数据结构,jdk8(包括8)之后是synchronized+cas+红黑树的数据结构。

下面我们先来比较下各种常见Map区别,然后研究两个版本的ConcurrentHashMap。

一、各种常见Map的比较

  1. HashMap:最基础的Map结构,在非并发场景下,优先使用。缺点是非线程安全的。
  2. HashTable:线程安全,内部方法都使用了synchronized加锁,操作比较耗性能。
  3. Collections.synchronizedMap():线程安全,与HashTable类型,都是使用synchronized对每个方法做了加锁,比较耗性能。
  4. ConcurrentHashMap:线程安全,操作效率较高。

二、JDK7版本的ConcurrentHashMap

1.Segment对象源码分析

JDK7以及之前版本的ConcurrentHashMap采用了分段锁Segment数组+Hash桶的数据结构,相当于是做了两次Hash。第一次Hash拿到分段锁,默认是16个Segment,每个Segment继承了ReentrantLock可以加锁,里面又维护了一个跟HashMap类似的Hash桶来保存key-value数据。结构图大致如下:
在这里插入图片描述

其核心在Segment类中,我们通过下面源码来分析。

可以看到Segment继承了重入锁ReentrantLock,所以它可以调用tryLock方法加锁。另外从其内部的代码结构可以看到,它也可以当成是一个HashMap。可以从put方法中看到,它就是调用了tryLock加锁,然后操作内部数据。可以理解是先加锁然后操作HashMap。

static final class Segment<K,V> extends ReentrantLock implements Serializable {
        // Segment的元素
        transient volatile HashEntry<K,V>[] table;
        // Segment中保存对象个数
        transient int count;    
        /** 扩容的阈值 */
        transient int threshold;
        /** 负载因子 */
        final float loadFactor;

        Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
            this.loadFactor = lf;
            this.threshold = threshold;
            this.table = tab;
        }

        final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
               // 这里省略部分代码
               .......
            } finally {
                unlock();
            }
            return oldValue;
        }

2.put方法分析

put方法先是通过hash找到当前key所对应的Segment,然后调用这个Segment对象的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;
        /** 内部维护了一个Segment的数组,默认数组大小为16,
        ConcurrentHashMap初始化的时候会把下标为0的Segment放到数组中,
        后面根据j的值从Segment数组中找到对应下标的Segment,
        如果找不到就创建一个Segment,并放到数组中 */
        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);
    }

3.size方法

size方法会遍历每个segment,并对其加锁,然后把每个segment保存的对象数量相加,最后解锁。

4.get方法

get方法不做加锁,首先拿到Segment,然后再去Segment里面的HashEntry数组里找到value

三、JDK8版本的ConcurrentHashMap

JDK8版本的ConcurrentHashMap比较复杂,这里就根据put方法的实现来大概来分析下其结构

 public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        // key对应的hash值
        int hash = spread(key.hashCode());
        int binCount = 0;
        // table是哈希桶,跟HashMap类似
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            // 找到hash对应的桶
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                // 桶中之前没值,把当前值插入成功后可直接退出轮询
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                // 对操作的节点加锁,这里直接使用了synchronized,可以猜测jdk8对synchronized做了很多优化,可以直接使用在并发类了
                synchronized (f) {
                	// 加锁之后就是构建链表或者红黑树,如果哈希桶中一个下标位置对应的节点数大于等于8就构建红黑树
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                	// 链表数量大于等于8构建红黑树
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值