ConcurrentHashMap-Jdk7-源码解读

本文详细解析了JDK7中ConcurrentHashMap的实现原理,包括其数据结构、HashEntry、Segment以及核心方法如put、remove和size。ConcurrentHashMap通过Segment数组和HashEntry链表实现高并发访问,每个Segment继承自ReentrantLock,通过分段锁确保并发安全性。在put操作中,使用scanAndLockForPut方法尝试加锁和寻找目标位置,rehash方法处理扩容。size()方法通过多次尝试避免加锁,实现弱一致性。整个结构保证了高效并发的同时,提供了良好的扩展性。

ConcurrentHashMap-Jdk7-源码解读

  • 以下代码截自Jdk-7u80
  • 关于Unsafe的知识了解不多,本文不多讨论,欢迎大佬教学
  • 文章中会将Segment叫做段,HashEntry[]数组叫做桶数组

1. ConcurrentHashMap数据结构

ConcurrentHashMap-Jdk7-数据结构
ConcurrentHashMap本质为一个Segment数组,Segment继承了ReentrantLock类,即Segment类自带一把锁,在操作数据时对当前segment加锁以保证并发安全,这就是ConcurrentHashMap支持高并发的分段锁原理(在Jdk1.7中)。
Segment是一个哈希数组(数据结构与1.7中的HashMap类似),组内元素为链表形式的HashEntry对象。
HashEntry是最终的键值对结构。
数据结构从小到大为:HashEntry < Segment < ConcurrentHashMap

2. HashEntry

HashEntryConcurrentHashMap数据结构中的最小存储单元,对应<K,V>键值对,通过next指向下一个节点,数据结构相对简单:

static final class HashEntry<K,V> {
	final int hash;// key的hash值
    final K key;
    volatile V value;
    volatile HashEntry<K,V> next;// 指向下一个节点
    HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
    // 设置Next节点
    final void setNext(HashEntry<K,V> n) {
        UNSAFE.putOrderedObject(this, nextOffset, n);
    }
	// Unsafe 相关的方法
    // Unsafe mechanics
    static final sun.misc.Unsafe UNSAFE;
    static final long nextOffset;// next成员的偏移地址
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class k = HashEntry.class;
            // 得到next成员在对象中的偏移量,用来进行链接操作
            // 利用UnSafe类可以通过直接操作内存来提高速度
            nextOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("next"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

3. Segment

Segment继承了ReentrantLock,可以通过加锁来保证线程安全。Segment的底层也是由数组实现,数组中为HashEntry构成的链表结构(整体结构与HashMap相似)。

3.1 成员变量/类变量

// 最大重试次数,在 scanAndLockForPut()、 scanAndLock() 方法中用到
// availableProcessors()返回java虚拟机的可用处理器数
static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
// 键值对数组
transient volatile HashEntry<K,V>[] table;
// 当前segment的键值对数量
transient int count;
// 操作数,该数据只增不减
transient int modCount;
// 扩容阈值  table.length * loadFactor = threshold
transient int threshold;
// 负载因子
final float loadFactor;

在ConcurrentHashMap中有一些方法不是直接加锁执行,而是进行多次遍历,通过modCount判断是否发生改变,循环次数通过MAX_SCAN_RETRIES来控制。

3.2 主要方法

put()

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;
		// 计算key在当前 Segment 中的数组下标
        int index = (tab.length - 1) & hash;
		// 获取第一个元素
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {// 判断key是否一致(== 或者 hash相等&&equals相等)
                    oldValue = e.value;
					// 根据传入参数判断是否需要替换,true表示相同key时不替换,false表示替换
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;// 操作数++
                    }
                    break;// 找到了目标key
                }
                e = e.next;
            }
            else {// 数组为空或者遍历到链表的最后也没找到同key
                if (node != null)// node不为空,说明 scanAndLockForPut 方法存在返回值
                    node.setNext(first);// 头插法
                else
                    node = new HashEntry<K,V>(hash, key, value, first);
                int c = count + 1;// 元素数量+1
				// 判断是否需要扩容(超过扩容阈值且长度小于最大值时可扩容)
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
					// 将元素node放入数组中
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();// 解锁
    }
    return oldValue;// oldValue为空则表示没有找到相同的key
}

scanAndLockForPut()/scanAndLock()

// 方法作用:创建新结点或找到key相同的结点并加锁,为添加做准备,用于put方法
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
	// 找到key对应的桶中的第一个元素
    HashEntry<K,V> first = entryForHash(this, hash);
    HashEntry<K,V> e = first;
    HashEntry<K,V> node = null;
	// 记录尝试次数
    int retries = -1; // negative while locating node
    while (!tryLock()) {
		HashEntry<K,V> f; // to recheck first below
        if (retries < 0) {
			// 桶为空或已到最后
            if (e == null) {
                if (node == null) // speculatively create node
                    node = new HashEntry<K,V>(hash, key, value, null);
                retries = 0;
            }
            else if (key.equals(e.key))// 找到了equals一致的key
                retries = 0;
            else
                e = e.next;
        }
        // 如果扫描次数大于阈值,则强制获取锁
		else if (++retries > MAX_SCAN_RETRIES) {
            lock();// 超过最大尝试次数就强制加锁,若获取不了锁,则会阻塞(对本Segment对象加锁)
            break;
        }
		// (retries & 1) == 0,当retries最低位不为1时成立
		// TODO ??
		// 有一种情况下会死循环:假设key每次都能找到equals一样的,retries=0,下次循环++retries后&1为true,若一直在修改first(put或者替换),则需要一直重置
        else if ((retries & 1) == 0 &&
				// 重新计算桶的第一个元素,如果!=first,表示当前桶被修改过,则需要重新循环
                 (f = entryForHash(this, hash)) != first) {
            e = first = f; // re-traverse if entry changed
            retries = -1;
        }
    }
	// 返回hash对应节点的位置
    return node;
}

// 这个方法跟上边的方法基本类似,只是少了创建新节点的步骤,用在remove、replace方法
private void scanAndLock(Object key, int hash) {
    // similar to but simpler than scanAndLockForPut
    HashEntry<K,V> first = entryForHash(this, hash);
    HashEntry<K,V> e = first;
    int retries = -1;
    while (!tryLock()) {
        HashEntry<K,V> f;
        if (retries < 0) {
            if (e == null || key.equals(e.key))
                retries = 0;
            else
                e = e.next;
        }
        else if (++retries > MAX_SCAN_RETRIES) {
            lock();
            break;
        }
        else if ((retries & 1) == 0 &&
                 (f = entryForHash(this, hash)) != first) {
            e = first = f;
            retries = -1;
        }
    }
}

rehash()

// segment.table扩容
@SuppressWarnings("unchecked")
private void rehash(HashEntry<K,V> node) {
    HashEntry<K,V>[] oldTable = table;
    int oldCapacity = oldTable.length;
	// 扩容一倍
    int newCapacity = oldCapacity << 1;
	// 计算新阈值
    threshold = (int)(newCapacity * loadFactor);
	// 初始化新数组
    HashEntry<K,V>[] newTable =
        (HashEntry<K,V>[]) new HashEntry[newCapacity];
	// length - 1,后续用来计算 hash&(length-1) 确定所在桶
    int sizeMask = newCapacity - 1;
    for (int i = 0; i < oldCapacity ; i++) {
        HashEntry<K,V> e = oldTable[i];// e表示当前桶位的第一个元素
        if (e != null) {
            HashEntry<K,V> next = e.next;// 获得链表下一个元素
            int idx = e.hash & sizeMask;
            if (next == null)   //  Single node on list
                newTable[idx] = e;// 当前链表只有一个元素,直接放入目标桶
            else { // Reuse consecutive sequence at same slot
				// 链表不只一个元素,需要遍历
                HashEntry<K,V> lastRun = e;// lastRun,从lastRun到链表尾部的元素都是一个桶,算是个优化吧
                int lastIdx = idx;// lastIdx,lastRun的桶
                for (HashEntry<K,V> last = next;
                     last != null;
                     last = last.next) {
                    int k = last.hash & sizeMask;// 计算桶位
                    if (k != lastIdx) {// ①
                        lastIdx = k;
                        lastRun = last;
                    }
                }
                newTable[lastIdx] = lastRun;// ② 这部分数据不用走之后的循环
                // Clone remaining nodes
				// 循环clone剩下的元素
				// 为啥要克隆?不能直接用么?
                for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                    V v = p.value;
                    int h = p.hash;
                    int k = h & sizeMask;// 重定位
                    HashEntry<K,V> n = newTable[k];// 获取当前第一个元素
                    newTable[k] = new HashEntry<K,V>(h, p.key, v, n);// 新建元素,next指向n,放入桶中
                }
            }
        }
    }
	// 插入node元素
    int nodeIndex = node.hash & sizeMask; // add the new node
    node.setNext(newTable[nodeIndex]);
    newTable[nodeIndex] = node;
    table = newTable;
}

代码中①处比较难以理解,用以下案例来做个说明:
当前桶位数:16,扩容后桶位数:32
计算桶位的算法:ex.index = x & (length - 1) = x % (length - 1)
segment.rehash转移方法
追踪一下lastRun、lastIdx变化的过程:

lastRunlastIdxlastk说明
e2ee1818此时k!=lastIdx,改变lastRun、lastIdx的值
e1818e342此时k!=lastIdx,改变lastRun、lastIdx的值
e342e662此时k=lastIdx,开始下一次循环
e342e982此时k=lastIdx

newTable[lastIdx] = lastRun
将最后的下标为lastIdx的节点直接放到新数组的lastIdx节点下,然后遍历lastRun之前的节点,依次clone到新数组。
当一条链表的最后几个节点重定位的下标相同时,直接将这几个节点放到新的数组中。

remove()

// value为null时,只匹配key,否则两者都匹配
final V remove(Object key, int hash, Object value) {
	// 尝试加锁,加锁失败就循环加锁,且找到目标值
    if (!tryLock())
        scanAndLock(key, hash);
    V oldValue = null;
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;// 计算桶位
        HashEntry<K,V> e = entryAt(tab, index);// 找到目标桶的第一个元素
        HashEntry<K,V> pred = null;// pred 指向前一个元素
        while (e != null) {
            K k;
            HashEntry<K,V> next = e.next;
			// key相等或者  hash相等且key.equals相等
            if ((k = e.key) == key ||
                (e.hash == hash && key.equals(k))) {
                V v = e.value;
				// 如果value为空(只匹配key) || value相等 || equals相等,找到目标元素
                if (value == null || value == v || value.equals(v)) {
                    if (pred == null)
						// 桶内第一个就是,将next放到桶第一位
                        setEntryAt(tab, index, next);
                    else
						// 否则前一个指向next
                        pred.setNext(next);
                    ++modCount;// 操作数+1
                    --count;// 原子数-1
                    oldValue = v;// 返回旧值
                }
                break;
            }
			// 遍历下一个元素
            pred = e;
            e = next;
        }
    } finally {
        unlock();// 解锁
    }
    return oldValue;
}

replace()

// key+value判断
final boolean replace(K key, int hash, V oldValue, V newValue) {
	// 加锁
    if (!tryLock())
        scanAndLock(key, hash);
    boolean replaced = false;// 用来标记是否替换成功
    try {
        HashEntry<K,V> e;
        for (e = entryForHash(this, hash); e != null; e = e.next) {// 获取桶位,然后遍历
            K k;
            if ((k = e.key) == key ||
                (e.hash == hash && key.equals(k))) {// (key==) 或 (e.hash相等且key.equals相等)
                if (oldValue.equals(e.value)) {// 判断值.equals是否相等
                    e.value = newValue;
                    ++modCount;
                    replaced = true;
                }
                break;
            }
        }
    } finally {
        unlock();
    }
    return replaced;
}
// 只用key判断
final V replace(K key, int hash, V value) {};

clear()

// 清空段位
final void clear() {
    lock();
    try {
        HashEntry<K,V>[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            setEntryAt(tab, i, null);// 直接循环将桶位置null
        ++modCount;
        count = 0;
    } finally {
        unlock();
    }
}

4. ConcurrentHashMap

4.1 常量/变量

// 默认初始化容量,这个容量并不是最终的段数或者桶数
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;
// 段最小容量,即段内HashEntry数组长度
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
// 最大段数
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
// 加锁最大重试次数,size()方法有用到
static final int RETRIES_BEFORE_LOCK = 2;
// 用来确定哈希值中参与段定位的高位的位数
final int segmentShift;
// 在段内索引的移位值,用来确定段的索引
final int segmentMask;
// 段数组
final Segment<K,V>[] segments;

4.2 构造函数

public ConcurrentHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}

public ConcurrentHashMap(int initialCapacity,
                         float loadFactor, int concurrencyLevel) {
	// 校验参数
    if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
	// 设置隔离级别,即段数
    if (concurrencyLevel > MAX_SEGMENTS)
        concurrencyLevel = MAX_SEGMENTS;
    // Find power-of-two sizes best matching arguments
    int sshift = 0;
    int ssize = 1;// 表示段数组长度
    // 找到 ssize >= concurrencyLevel 的最小的2次幂
    while (ssize < concurrencyLevel) {
        ++sshift;
        ssize <<= 1;
    }
	// hash值中段所占的位数??,作用是什么?用来计算段的位置,不太清楚原理
    this.segmentShift = 32 - sshift;
	// 用来计算目标段下标  hash&(size-1)
    this.segmentMask = ssize - 1;
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;// 不能超过最大容量
	// c表示每个段的hash表长度
    int c = initialCapacity / ssize;
    if (c * ssize < initialCapacity)// 如果为浮点数,则向上+1
        ++c;
    int cap = MIN_SEGMENT_TABLE_CAPACITY;
    while (cap < c)
        cap <<= 1;// 每个段的hash表长度,2的次幂,最小为2
    // create segments and segments[0]
    Segment<K,V> s0 =
        new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                         (HashEntry<K,V>[])new HashEntry[cap]);
	// 创建段数组
    Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
    // ConcurrentHashMap是懒加载的方式,除了第一个段之外,其他的都是在用到的时候才初始化
	// 写入segments[0],put时会以segments[0]为原型创建新的segment,所以直接创建了第一个段
    UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
	// 赋值给段数组
    this.segments = ss;
}

public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
	// +1是为了向上取整,有可能造成一定空间浪费
    this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                  DEFAULT_INITIAL_CAPACITY),
         DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
    putAll(m);
}

4.3 主要方法

entryAt()

// 找到tab中第i个元素
static final <K,V> HashEntry<K,V> entryAt(HashEntry<K,V>[] tab, int i) {
    return (tab == null) ? null :
        (HashEntry<K,V>) UNSAFE.getObjectVolatile
        (tab, ((long)i << TSHIFT) + TBASE);
}

setEntryAt()

// 将 e 放到 tab 的第 i 个元素处
static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i,
                                   HashEntry<K,V> e) {
    UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e);
}

segmentAt()

// 找到组第j个段
static final <K,V> Segment<K,V> segmentAt(Segment<K,V>[] ss, int j) {
    long u = (j << SSHIFT) + SBASE;
    return ss == null ? null :
        (Segment<K,V>) UNSAFE.getObjectVolatile(ss, u);
}

put()

// key和value都不能是空的,若匹配到相同key,则替换并返回旧值
public V put(K key, V value) {
    Segment<K,V> s;
	// 这个地方并没有规定key不能为null,但是在hash(key)会爆出来空指针,为啥不直接指定呢?
    if (value == null)
        throw new NullPointerException();
	
    int hash = hash(key);// 扰动后的hash值,没看懂
	// 通过散列值的高n位来确定段下标
    int j = (hash >>> segmentShift) & segmentMask;
	// 根据物理地址来取得目标段
	// SSHIFT :Segment数组每个元素的偏移量,SBASE :Segment数组第一个元素的偏移量
	// 由上面两个偏移量可以算出目标下标元素的物理地址
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
		// 确认段信息,若段不存在,则创建段
        s = ensureSegment(j);
	// 调用 Segment.put
    return s.put(key, hash, value, false);
}
// 只是最后调用 Segment.put 传参不同,key相同时不再替换
public V putIfAbsent(K key, V value){}
// 循环调用put(注:不会动原有的entry,每次都是新建,但是key和value用的是原有的)
public void putAll(Map<? extends K, ? extends V> m) {}

ensureSegment()

// 返回k对应的段,若不存在就创建(CAS算法)
private Segment<K,V> ensureSegment(int k) {
    final Segment<K,V>[] ss = this.segments;
    long u = (k << SSHIFT) + SBASE; // raw offset
    Segment<K,V> seg;
    if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
		// 以segments[0]为原型
        Segment<K,V> proto = ss[0]; // use segment 0 as prototype
        int cap = proto.table.length;
        float lf = proto.loadFactor;
        int threshold = (int)(cap * lf);
        HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];// 创建segment.table
		// 二次检查是否有其它线程创建了这个Segment
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
            == null) { // recheck
			// 创建segment
            Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
			// 这里通过自旋的CAS方式对segments数组中偏移量为u位置设置值为s,这是一种不加锁的方式,
            // 万一有多个线程同时执行这一步,那么只会有一个成功,而其它线程在看到第一个执行成功的线程结果后
            // 会获取到最新的数据从而发现需要更新的坑位已经不为空了,那么就跳出while循环并返回最新的seg
            while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                   == null) {
                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                    break;
            }
        }
    }
    return seg;// 返回目标段
}

isEmpty()

public boolean isEmpty() {
    long sum = 0L;
    final Segment<K,V>[] segments = this.segments;
    for (int j = 0; j < segments.length; ++j) {
        Segment<K,V> seg = segmentAt(segments, j);
        if (seg != null) {
            if (seg.count != 0)// count不为空,表示存在元素,直接返回false
                return false;
            sum += seg.modCount;// 统计操作数
        }
    }
	// 二次检查操作数是否发生变化
    if (sum != 0L) { // recheck unless no modifications
        for (int j = 0; j < segments.length; ++j) {
            Segment<K,V> seg = segmentAt(segments, j);
            if (seg != null) {
                if (seg.count != 0)
                    return false;
                sum -= seg.modCount;
            }
        }
        if (sum != 0L)// 因为操作数是衡增的,两次统计中,若操作数发生变化,则表示源列表不为空
            return false;
    }
    return true;
}

size()

// 返回键值对的个数,如果超过了Integer.MAX_VALUE,则返回Integer.MAX_VALUE
public int size() {
	// 先不加锁尝试,超过RETRIES_BEFORE_LOCK次时,加锁统计
    // Try a few times to get accurate count. On failure due to
    // continuous async changes in table, resort to locking.
    final Segment<K,V>[] segments = this.segments;
    int size;
    boolean overflow; // true if size overflows 32 bits,true表示超过32位
    long sum;         // sum of modCounts,操作数之和
    long last = 0L;   // previous sum,上一次的操作数
    int retries = -1; // first iteration isn't retry,重试次数,第一次不算所以是-1
    try {
        for (;;) {
			// 判断重试次数,此处是给所有的段加锁
            if (retries++ == RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock(); // force creation,强行加锁
            }
            sum = 0L;
            size = 0;
            overflow = false;
            for (int j = 0; j < segments.length; ++j) {
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null) {
                    sum += seg.modCount;// 操作数
                    int c = seg.count;// 键值对数
                    if (c < 0 || (size += c) < 0)// int超过最大数之后就变成负的了,表示超过32位
                        overflow = true;
                }
            }
            if (sum == last)// 此次循环和上一次循环一致,则跳出循环
                break;
            last = sum;
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) {// 段解锁
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

get()

// key存在则返回对应的值,否则返回null
public V get(Object key) {
	// 段
    Segment<K,V> s; // manually integrate access methods to reduce overhead
    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) {//获取h对应这个分段中偏移量为xxx下的HashEntry的链表头结点,然后对链表进行 遍历
            // 这里第一次初始化通过getObjectVolatile获取HashEntry时,获取到的是主存中最新的数据,但是在后续遍历过程中,有可能数据被其它线程修改
            // 从而导致其实这里最终返回的可能是过时的数据,所以这里就是ConcurrentHashMap所谓的弱一致性的体现,containsKey方法也一样
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}

contains()

/** 如果找到一个或多个value,则返回true,否则返回value,需要遍历数组,会比较慢 */
public boolean containsValue(Object value) {
    // Same idea as size()
	// 参照size方法的方式
    if (value == null)
        throw new NullPointerException();
    final Segment<K,V>[] segments = this.segments;
    boolean found = false;// 结果
    long last = 0;
    int retries = -1;// 重试次数
    try {
        outer: for (;;) {
            if (retries++ == RETRIES_BEFORE_LOCK) {
				// 超过RETRIES_BEFORE_LOCK次时,加锁统计
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock(); // force creation
            }
            long hashSum = 0L;
            int sum = 0;
            for (int j = 0; j < segments.length; ++j) {
                HashEntry<K,V>[] tab;
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null && (tab = seg.table) != null) {
                    for (int i = 0 ; i < tab.length; i++) {
                        HashEntry<K,V> e;
                        for (e = entryAt(tab, i); e != null; e = e.next) {
                            V v = e.value;
                            if (v != null && value.equals(v)) {
                                found = true;
                                break outer;
                            }
                        }
                    }
                    sum += seg.modCount;
                }
            }
            if (retries > 0 && sum == last)
                break;
            last = sum;
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) {
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return found;
}
// 就是containsValue,遗留方法
public boolean contains(Object value) {}
// map中是否包含key,跟get差不多,只是返回boolean
public boolean containsKey(Object key) {}

其他方法

以下方法基本都是定位段,然后调用segment对应的方法,不再赘述:

  • public V remove(Object key) {}
  • public boolean remove(Object key, Object value) {}
  • public boolean replace(K key, V oldValue, V newValue) {}
  • public V replace(K key, V value) {}
  • public void clear() {}

5. 总结

  • ConcurrentHashMap由Segment数组结构和HashEntry数组组成。Segment是一种可重入锁,是一种数组和链表的结构,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构。正是通过Segment分段锁,ConcurrentHashMap实现了高效率的并发;
  • 段数为大于等于concurrencyLevel的最小的2次幂,段的hash表的最小长度为2;
  • ConcurrentHashMap的段数量在初始化后不会改变,扩容至会发生在段内,2倍扩容;
  • ConcurrentHashMap存在弱一致性,get()、containsKey()(这两个方法没有加锁)可能读到旧的结果;
  • ConcurrentHashMap创建segment时,采用CAS算法;

6. 参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值