Java常见集合框架(二十一): Map之ConcurrentMap、ConcurrentHashMap

##ConcurrentMap

public interface ConcurrentMap extends Map

  1. 提供其他原子 putIfAbsent、remove、replace 方法的 Map。
  2. 内存一致性效果:当存在其他并发 collection 时,将对象放入 ConcurrentMap 之前的线程中的操作 happen-before 随后通过另一线程从 ConcurrentMap 中访问或移除该元素的操作。

接口方法

    /**
     *  如果指定键已经不再与某个值相关联,则将它与给定值关联。
     */
    V putIfAbsent(K key, V value);

    /**
     *  只有目前将键的条目映射到给定值时,才移除该键的条目。
     */
    boolean remove(Object key, Object value);

    /**
     *  只有目前将键的条目映射到某一值时,才替换该键的条目。
     */
    boolean replace(K key, V oldValue, V newValue);

    /**
     *  只有目前将键的条目映射到给定值时,才替换该键的条目。
     */
    V replace(K key, V value);

##ConcurrentHashMap

public class ConcurrentHashMap extends AbstractMap implements ConcurrentMap, Serializable

  1. 支持获取的完全并发和更新的所期望可调整并发的哈希表。
  2. 不允许将 null 用作键或值。
  3. 适用于读多写少场景。

成员变量

 /* ---------------- Constants -------------- */

    /**
     * 默认初始容量
     */
    static final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * 默认装载因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 默认更新并发线程数
     */
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;

    /**
     * 最大容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

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

    /**
     * 锁之前重试次数
     */
    static final int RETRIES_BEFORE_LOCK = 2;

    /* ---------------- Fields -------------- */

    /**
     * 段标记
     */
    final int segmentMask;

    /**
     * 段的偏移量
     */
    final int segmentShift;

    /**
     * 特殊table-段
     */
    final Segment<K,V>[] segments;

    transient Set<K> keySet;
    transient Set<Map.Entry<K,V>> entrySet;
    transient Collection<V> values;

    /* ---------------- Small Utilities -------------- */

构造方法

//创建一个带有默认初始容量 (16)、加载因子 (0.75) 和 concurrencyLevel (16) 的新的空映射。
ConcurrentHashMap() 

// 创建一个带有指定初始容量、默认加载因子 (0.75) 和 concurrencyLevel (16) 的新的空映射。          
ConcurrentHashMap(int initialCapacity) 

// 创建一个带有指定初始容量、加载因子和默认 concurrencyLevel (16) 的新的空映射。         
ConcurrentHashMap(int initialCapacity, float loadFactor) 

//创建一个带有指定初始容量、加载因子和并发级别的新的空映射。
ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) 

//构造一个与给定映射具有相同映射关系的新映射。 
ConcurrentHashMap(Map<? extends K,? extends V> m) 
          

常用核心方法

V put(K key, V value) :将指定键映射到此表中的指定值。键和值都不可以为 null。返回以前与 key 相关联的值,如果 key 没有映射关系,则返回 null。

    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);
    }

V putIfAbsent(K key, V value):如果指定键已经不再与某个值相关联,则将它与给定值关联。返回以前与指定键相关联的值,如果该键没有映射关系,则返回 null。

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

final Segment segmentFor(int hash):根据哈希码计算对应的段segment。

    final Segment<K,V> segmentFor(int hash) {
        return segments[(hash >>> segmentShift) & segmentMask];
    }

V put(K key, int hash, V value, boolean onlyIfAbsent):Segment内部类方法。

static final class Segment<K,V> extends ReentrantLock implements Serializable {
	
	V put(K key, int hash, V value, boolean onlyIfAbsent) {
            lock();//调用父类获取锁方法
            try {
                int c = count;
                if (c++ > threshold) // 判断是否需要扩容
                    rehash();
                HashEntry<K,V>[] tab = table;
                int index = hash & (tab.length - 1);//计算索引
                HashEntry<K,V> first = tab[index];//获取到对应的链表
                HashEntry<K,V> e = first;
                //遍历链表,获取到对应元素
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;

                V oldValue;
                if (e != null) {//已存在对应key-value
                    oldValue = e.value;
                    if (!onlyIfAbsent)//判断是否更新value
                        e.value = value;
                }
                else {//key首次put
                    oldValue = null;
                    ++modCount;//修改次数+1
                    tab[index] = new HashEntry<K,V>(key, hash, first, value);
                    count = c; // write-volatile
                }
                return oldValue;
            } finally {
                unlock();//释放锁
            }
        }
}

V remove(Object key):从此映射中移除键(及其相应的值)。

    public V remove(Object key) {
		int hash = hash(key.hashCode());
        return segmentFor(hash).remove(key, hash, null);
    }

boolean remove(Object key, Object value):只有目前将键的条目映射到给定值时,才移除该键的条目。

    public boolean remove(Object key, Object value) {
        int hash = hash(key.hashCode());
        if (value == null)
            return false;
        return segmentFor(hash).remove(key, hash, value) != null;
    }

V remove(Object key, int hash, Object value):Segment内部类方法。

static final class Segment<K,V> extends ReentrantLock implements Serializable {
	
	 V remove(Object key, int hash, Object value) {
            lock();//获取锁
            try {
                int c = count - 1;
                HashEntry<K,V>[] tab = table;
                int index = hash & (tab.length - 1);
                HashEntry<K,V> first = tab[index];
                HashEntry<K,V> e = first;
                //找对应key-value
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;

                V oldValue = null;
                if (e != null) {//有对应key-value
                    V v = e.value;
                    if (value == null || value.equals(v)) {
                        oldValue = v;
                        ++modCount;//修改次数+1
                        HashEntry<K,V> newFirst = e.next;
                        //next由final修饰不可变,故移除的节点前所有元素均需要clone。
            for (HashEntry<K,V> p = first; p != e; p = p.next)
newFirst = new HashEntry<K,V>(p.key, p.hash, newFirst, p.value);//                                                 
                        tab[index] = newFirst;
                        count = c; // write-volatile
                    }
                }
                return oldValue;
            } finally {
                unlock();
            }
        }
}

内部类HashEntry
由于key、value、及next均由final声明,故执行remvoe操作后,移除元素的前一个元素对应entry.next值应该变了,但由于final定义,导致不能再改变它,故将它之前的节点全都克隆一次。

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

V get(Object key):返回指定键所映射到的值,如果此映射不包含该键的映射关系,则返回 null。

    public V get(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).get(key, hash);
    }

V get(Object key, int hash) :Segment内部方法。

        V get(Object key, int hash) {
            if (count != 0) { // read-volatile 有key-value
                HashEntry<K,V> e = getFirst(hash);//找到对应的链表,并返回头节点
                //遍历链表,找到对应键值对
                while (e != null) {
                    if (e.hash == hash && key.equals(e.key)) {
                        V v = e.value;
                        if (v != null)
                            return v;
                        return readValueUnderLock(e); // recheck
                    }
                    e = e.next;
                }
            }
            return null;
        }
        
        HashEntry<K,V> getFirst(int hash) {
            HashEntry<K,V>[] tab = table;
            return tab[hash & (tab.length - 1)];
        }

总结:

  1. ConcurrentHashMap采用分段锁设计(Segment),默认初始将Map分为16段即Segment[] segments数组长度16,也可理解为写线程并发度为16。
  2. Segment extends ReentrantLock则每个分段Segment都拥有自己的ReentrantLock,减少锁竞争。
  3. 如果并发度设置的过小,会带来严重的锁竞争问题;如果并发度设置的过大,原本位于同一个Segment内的访问会扩散到不同的Segment中,CPU cache命中率会下降,从而引起程序性能下降。
  4. Segment中成员变量:transient volatile int count;表示对应Segment中key-value元素个数,当count==0时,get操作直接返回,避免不必要的査找开销。
  5. Segment中有个重要成员变量:volatile transient ConcurrentHashMap.HashEntry[] table。每个Segment中key-value都以HashEntry实例存入到该数组中,若有哈希冲突,HashEntry是个链表,以链地址法解决即将哈希地址相同的key,放入到同一个链表中。

put or get or remove主要步骤:

  1. 根据key的哈希码再哈希。int hash = hash(key.hashCode())
  2. 根据新哈希码找到对应segment。segmentFor(hash)
  3. 在对应segment中计算HashEntry的索引 int index = hash & (tab.length - 1);
  4. 遍历对应 HashEntry[] table的table[index]
  5. 匹配HashEntry的哈希码,及key,匹配成功进行对应操作。

jdk1.8新变化:

  1. Segment改为Node,锁住node来实现减小锁粒度。
  2. synchronize替代了ReentrantLock。
  3. 利用Unsafe的CAS,实现无锁化的修改值的操作。
  4. Node中next去掉了final修饰,增加了violate修饰,避免了remove后clone问题。
  5. 设计了MOVED状态 当resize的中过程中 线程2还在put数据,线程2会帮助resize。
  6. 当链表节点大于等于8时,把链表节点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值