ConcurrentHashMap详解

目录

ConcurrentHashMap介绍

ConcurrentHashMap底层数据结构

ConcurrentHashMap部分分析

ConcurrentHashMap与HashMap、HashTable的区别


源码为jdk1.7

ConcurrentHashMap介绍

     ConcurrentHashMap 是concurrent包下的一个集合类。它是线程安全的哈希表。它是通过“分段锁”来实现多线程下的安全问题。它将哈希表分成了不同的段内使用了可重入锁(ReentrantLock ),不同线程只在一个段内存在线程的竞争。它不会对整个哈希表加锁。

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable {

它继承了AbstractMap类,实现了ConcurrentMap,Serializable接口

public abstract class AbstractMap<K,V> implements Map<K,V> {

可以看出AbstractMap类继承了Map接口,也就是说存放着键值对,还有实现了Map接口那些方法。

ConcurrentHashMap底层数据结构

ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构。它内部拥有一个HashEntry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

 static final class Segment<K,V> extends ReentrantLock implements Serializable {

Segment继承了可重入锁。

static final class HashEntry<K,V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K,V> next;

        HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
            this.key = key;
            this.hash = hash;
            this.next = next;
            this.value = value;
        }

	@SuppressWarnings("unchecked")
	static final <K,V> HashEntry<K,V>[] newArray(int i) {
	    return new HashEntry[i];
	}
    }

HashEntry这个类有key、value、next、hash四个属性。HashEntry中的value被volatile修饰,这样在多线程读写过程中能够保持它们的可见性。

ConcurrentHashMap部分分析

ConcurrentHashMap具有的属性

    //默认容量大小
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    //默认加载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //默认并发数量(segments数组的大小)
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    //最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //最大并发数量
    static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
    static final int RETRIES_BEFORE_LOCK = 2;
    final int segmentMask;
    final int segmentShift;
    //segments数组
    final Segment<K,V>[] segments;
    //迭代器相关
    transient Set<K> keySet;
    transient Set<Map.Entry<K,V>> entrySet;
    transient Collection<V> values;

构造函数 

public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        //参数的有效性判断
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        //如果concurrencyLevel大于最大并发量,将其值设为最大并发量。(segments数组的大小)
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;

        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        int ssize = 1;
        //sshift记录ssize左移的次数
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        segmentShift = 32 - sshift;
        segmentMask = ssize - 1;
        this.segments = Segment.newArray(ssize);

        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //c为每个为table数组分配的容量
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = 1;
        while (cap < c)
            cap <<= 1;

        for (int i = 0; i < this.segments.length; ++i)
            this.segments[i] = new Segment<K,V>(cap, loadFactor);
    }

 concurrencyLevel的作用就是用来计算segments数组的容量大小。先计算出“大于或等于concurrencyLevel的最小的2的N次方值”,然后将其保存为“segments的容量大小(ssize)”。

put()方法

public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key.hashCode());
        return segmentFor(hash).put(key, hash, value, false);
    }

如果value为null,抛出异常。不允许值为空。通过key得到hash值。将key-value键值对添加到Segment片段中。

 final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            //尝试获得锁,如果失败则通过scanAndLockForPut方法获得锁
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
            //  根据”hash值“获取”HashEntry数组中对应的HashEntry链表“
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        //如果key重复,则覆盖旧值
                        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 {
                    //否则创建新的HashEntry节点
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                    //如果容量大于阈值,需要重hash
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
            //解锁
                unlock();
            }
            return oldValue;
        }

put()的作用是将key-value键值对插入到“当前Segment对应的HashEntry中”,在插入前它会获取Segment对应的互斥锁,插入后会释放锁。

rehash()的作用是将”Segment的容量“变为”原始的Segment容量的2倍“。
在将原始的数据拷贝到“新的Segment”中后,会将新增加的key-value键值对添加到“新的Segment”中。

get方法

获取key值对应的hash值,在对应的segment里面获取 。

public V get(Object key) {
    Segment<K,V> s;
    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;
}
 
 

可以看到get方法并没有加锁

get(Object key)的作用是返回key在ConcurrentHashMap里的值。首先根据key计算出hash值,获取key对应的Segment片段。如果Segment不为null,则在Segment片段的HashEntry链表中遍历这个链表找到对应的HashEntry节点。

由于遍历过程中其他线程可能对链表结构做了调整,因此get和containsKey返回的可能是过时的数据,这一点是ConcurrentHashMap在弱一致性上的体现。

ConcurrentHashMap与HashMap、HashTable的区别

  1. ConcurrentHashMap 、HashTable不允许值为null,值为null时抛出异常,HashMap允许值为null
  2. HashTable所有方法都加锁synchronized,为线程安全的。HashMap不保证线程安全。ConcurrentHashMap采用了分段锁,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
  3. ConcurrentHashMap为弱一致性,在get方法时有可能获得过时的数据。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值