ConcurrentHashMap

来源:互联网 发布:安卓手机游戏源码 编辑:程序博客网 时间:2024/06/05 22:42
ConcurrentHashMap
1.实现原理
2.数据结构
3.增删改查

一.实现原理
锁分离   
ConcurrentHashMap使用分段锁技术,将数据分段存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,
其他段的数据也能被其他线程访问,能够实现真正的并发访问。

对于一个key,需要经过三次(为什么要hash三次下文会详细讲解)hash操作,才能最终定位这个元素的位置,这三次hash分别为:

对于一个key,先进行一次hash操作,得到hash值h1,也即h1 = hash1(key);
将得到的h1的高几位进行第二次hash,得到hash值h2,也即h2 = hash2(h1高几位),通过h2能够确定该元素的放在哪个Segment;
将得到的h1进行第三次hash,得到hash值h3,也即h3 = hash3(h1),通过h3能够确定该元素放置在哪个HashEntry。

二.数据结构

public class ConcurrentHashMap<K, V> {    final int segmentMask; // 段掩码 等于数组长度-1 默认为15    final int segmentShift; // 段偏移量 等于 32 - 数组长度从1向左移位的次数, 长度默认为16所以默认为32-4=28    final Segment<K,V>[] segments;}static final class Segment<K,V> extends ReentrantLock{    transient volatile int count; // 统计该段数据的个数    transient int modCount; // 统计段结构改变的次数    transient int threshold; // 表示需要进行rehash的界限值    transient volatile HashEntry<K,V>[] table;    final float loadFactor; // 表示负载因子}static final class HashEntry<K,V> {    final K key;    final int hash;    volatile V value;    final HashEntry<K,V> next;}


除了value不是final的,其它值都是final的,因此所有的节点的修改只能从头部开始

三.增删改查
remove
1.定位到segment
2.加锁
3.定位到要删除的数据

4.将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。

public V remove(Object key) {<span style="white-space:pre"></span>int hash = hash(key.hashCode());<span style="white-space:pre"></span>return segmentFor(hash).remove(key, hash, null);}        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;                while (e != null && (e.hash != hash || !key.equals(e.key)))                    e = e.next;                V oldValue = null;                if (e != null) {                    V v = e.value;                    if (value == null || value.equals(v)) {                        oldValue = v;                        // All entries following removed node can stay                        // in list, but all preceding ones need to be                        // cloned.                        ++modCount;                        HashEntry<K,V> newFirst = e.next;                        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();            }        }



put
1.通过hash算法得到对应的分段
2.向分段中push值
3.判断是否需要rehash
4.修改modcount
5.插入值,一律添加到Hash链的头部。
6.修改count



get
1.定位到segment
2.阅读count,保证happens_before(不是绝对安全)
3.获得值
4.如果为空则加锁重新读

size
1.先给3次机会,不lock所有的Segment,遍历所有Segment,累加各个Segment的大小得到整个Map的大小,
如果某相邻的两次计算获取的所有Segment的更新的次数(每个Segment都有一个modCount变量,
这个变量在Segment中的Entry被修改时会加一,通过这个值可以得到每个Segment的更新操作的次数)是一样的,
说明计算过程中没有更新操作,则直接返回这个值。

2.如果这三次不加锁的计算过程中Map的更新次数有变化,则之后的计算先对所有的Segment加锁,再遍历所有Segment计算Map大小,最后再解锁所有Segment
0 0