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

来源:互联网 发布:图片相似度比较算法 编辑:程序博客网 时间:2024/05/20 20:57

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完成对红黑树的包装。

阅读全文
0 0