Java ConcurrentHashMap源码分析

发布时间:2023-03-02 10:09:43 作者:iii
来源:亿速云 阅读:130

Java ConcurrentHashMap源码分析

目录

  1. 引言
  2. ConcurrentHashMap概述
  3. ConcurrentHashMap的核心数据结构
  4. ConcurrentHashMap的初始化
  5. ConcurrentHashMap的put操作
  6. ConcurrentHashMap的get操作
  7. ConcurrentHashMap的remove操作
  8. ConcurrentHashMap的扩容机制
  9. ConcurrentHashMap的并发控制
  10. ConcurrentHashMap的迭代器
  11. ConcurrentHashMap的性能分析
  12. ConcurrentHashMap的常见问题
  13. 总结

引言

在多线程环境下,HashMap并不是线程安全的,因此在并发场景下使用HashMap可能会导致数据不一致的问题。为了解决这个问题,Java提供了ConcurrentHashMap,它是HashMap的线程安全版本。ConcurrentHashMap通过分段锁(Segment)和CAS操作来实现高效的并发控制,从而在多线程环境下提供高性能的读写操作。

本文将深入分析ConcurrentHashMap的源码,探讨其内部实现机制,包括数据结构、初始化、put操作、get操作、remove操作、扩容机制、并发控制、迭代器以及性能分析等方面。

ConcurrentHashMap概述

ConcurrentHashMap是Java集合框架中的一个重要类,它实现了ConcurrentMap接口,提供了线程安全的哈希表实现。与HashMap不同,ConcurrentHashMap通过分段锁(Segment)和CAS操作来实现高效的并发控制,从而在多线程环境下提供高性能的读写操作。

ConcurrentHashMap的主要特点包括:

ConcurrentHashMap的核心数据结构

ConcurrentHashMap的核心数据结构是一个数组,数组中的每个元素是一个Node节点。Node节点是ConcurrentHashMap的基本存储单元,它包含了键值对以及指向下一个节点的指针。

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;

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

    public final K getKey()       { return key; }
    public final V getValue()     { return val; }
    public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
    public final String toString(){ return key + "=" + val; }
    public final V setValue(V value) {
        throw new UnsupportedOperationException();
    }
}

ConcurrentHashMap的数组是一个Node数组,数组中的每个元素是一个Node节点。Node节点是ConcurrentHashMap的基本存储单元,它包含了键值对以及指向下一个节点的指针。

transient volatile Node<K,V>[] table;

ConcurrentHashMap还包含一个Segment数组,SegmentConcurrentHashMap的分段锁,每个Segment包含一个HashEntry数组,HashEntrySegment的基本存储单元。

final Segment<K,V>[] segments;

SegmentConcurrentHashMap的分段锁,每个Segment包含一个HashEntry数组,HashEntrySegment的基本存储单元。

static final class Segment<K,V> extends ReentrantLock implements Serializable {
    private static final long serialVersionUID = 2249069246763182397L;
    transient volatile HashEntry<K,V>[] table;
    transient int count;
    transient int modCount;
    transient int threshold;
    final float loadFactor;

    Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
        this.loadFactor = lf;
        this.threshold = threshold;
        this.table = tab;
    }
}

HashEntrySegment的基本存储单元,它包含了键值对以及指向下一个节点的指针。

static final class HashEntry<K,V> {
    final int 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;
    }
}

ConcurrentHashMap的初始化

ConcurrentHashMap的初始化过程主要包括以下几个步骤:

  1. 计算初始容量ConcurrentHashMap的初始容量是根据传入的initialCapacityconcurrencyLevel参数计算的。
  2. 创建Segment数组ConcurrentHashMapSegment数组是根据concurrencyLevel参数创建的。
  3. 创建HashEntry数组:每个Segment包含一个HashEntry数组,HashEntry数组的容量是根据initialCapacityconcurrencyLevel参数计算的。
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;
    int sshift = 0;
    int ssize = 1;
    while (ssize < concurrencyLevel) {
        ++sshift;
        ssize <<= 1;
    }
    this.segmentShift = 32 - sshift;
    this.segmentMask = ssize - 1;
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    int c = initialCapacity / ssize;
    if (c * ssize < initialCapacity)
        ++c;
    int cap = MIN_SEGMENT_TABLE_CAPACITY;
    while (cap < c)
        cap <<= 1;
    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];
    UNSAFE.putOrderedObject(ss, SBASE, s0);
    this.segments = ss;
}

ConcurrentHashMap的put操作

ConcurrentHashMapput操作主要包括以下几个步骤:

  1. 计算哈希值:首先计算键的哈希值。
  2. 定位Segment:根据哈希值定位到对应的Segment
  3. 加锁:对Segment加锁,保证线程安全。
  4. 插入节点:在SegmentHashEntry数组中插入新的节点。
  5. 释放锁:释放Segment的锁。
public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)) == null)
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}

Segmentput操作主要包括以下几个步骤:

  1. 计算哈希值:首先计算键的哈希值。
  2. 定位HashEntry:根据哈希值定位到对应的HashEntry
  3. 插入节点:在HashEntry链表中插入新的节点。
  4. 更新计数:更新Segment的计数。
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        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))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            } else {
                if (node != null)
                    node.setNext(first);
                else
                    node = new HashEntry<K,V>(hash, key, value, first);
                int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}

ConcurrentHashMap的get操作

ConcurrentHashMapget操作主要包括以下几个步骤:

  1. 计算哈希值:首先计算键的哈希值。
  2. 定位Segment:根据哈希值定位到对应的Segment
  3. 定位HashEntry:根据哈希值定位到对应的HashEntry
  4. 查找节点:在HashEntry链表中查找对应的节点。
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;
}

ConcurrentHashMap的remove操作

ConcurrentHashMapremove操作主要包括以下几个步骤:

  1. 计算哈希值:首先计算键的哈希值。
  2. 定位Segment:根据哈希值定位到对应的Segment
  3. 加锁:对Segment加锁,保证线程安全。
  4. 删除节点:在SegmentHashEntry数组中删除对应的节点。
  5. 释放锁:释放Segment的锁。
public V remove(Object key) {
    int hash = hash(key);
    Segment<K,V> s = segmentForHash(hash);
    return s == null ? null : s.remove(key, hash, null);
}

Segmentremove操作主要包括以下几个步骤:

  1. 计算哈希值:首先计算键的哈希值。
  2. 定位HashEntry:根据哈希值定位到对应的HashEntry
  3. 删除节点:在HashEntry链表中删除对应的节点。
  4. 更新计数:更新Segment的计数。
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> first = entryAt(tab, index);
        HashEntry<K,V> e = first;
        while (e != null && (e.hash != hash || !key.equals(e.key)))
            e = e.next;
        if (e != null) {
            V v = e.value;
            if (value == null || value.equals(v)) {
                oldValue = v;
                ++modCount;
                HashEntry<K,V> newFirst = e.next;
                for (HashEntry<K,V> p = first; p != e; p = p.next)
                    newFirst = new HashEntry<K,V>(p.hash, p.key, p.value, newFirst);
                setEntryAt(tab, index, newFirst);
                count = c;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}

ConcurrentHashMap的扩容机制

ConcurrentHashMap的扩容机制主要包括以下几个步骤:

  1. 计算新容量:根据当前容量和负载因子计算新的容量。
  2. 创建新数组:创建一个新的HashEntry数组。
  3. 迁移数据:将旧数组中的数据迁移到新数组中。
  4. 更新数组:将新数组设置为当前数组。
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];
    int sizeMask = newCapacity - 1;
    for (int i = 0; i < oldCapacity ; i++) {
        HashEntry<K,V> e = oldTable[i];
        if (e != null) {
            HashEntry<K,V> next = e.next;
            int idx = e.hash & sizeMask;
            if (next == null)
                newTable[idx] = e;
            else {
                HashEntry<K,V> lastRun = e;
                int lastIdx = idx;
                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;
                for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                    int h = p.hash;
                    int k = h & sizeMask;
                    HashEntry<K,V> n = newTable[k];
                    newTable[k] = new HashEntry<K,V>(h, p.key, p.value, n);
                }
            }
        }
    }
    int nodeIndex = node.hash & sizeMask;
    node.setNext(newTable[nodeIndex]);
    newTable[nodeIndex] = node;
    table = newTable;
}

ConcurrentHashMap的并发控制

ConcurrentHashMap的并发控制主要通过分段锁(Segment)和CAS操作来实现。每个Segment是一个独立的锁,不同的Segment之间可以并发操作,从而提高了并发性能。

ConcurrentHashMap的并发控制主要包括以下几个方面:

  1. 分段锁ConcurrentHashMap通过分段锁来实现高效的并发控制。每个Segment是一个独立的锁,不同的Segment之间可以并发操作。
  2. CAS操作ConcurrentHashMap通过CAS操作来实现高效的并发控制。CAS操作是一种无锁操作,它通过比较并交换的方式来实现原子操作。
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        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))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            } else {
                if (node != null)
                    node.setNext(first);
                else
                    node = new HashEntry<K,V>(hash, key, value, first);
                int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}

ConcurrentHashMap的迭代器

ConcurrentHashMap的迭代器主要包括以下几个步骤:

  1. 创建迭代器:创建一个HashEntry迭代器。
  2. 遍历节点:遍历HashEntry链表中的节点。
  3. 返回节点:返回当前节点。

”`java public Iterator> iterator() { return new EntryIterator(); }

final class EntryIterator extends HashIterator implements Iterator> { public Map.Entry next() { HashEntry e = super.nextEntry(); return new WriteThroughEntry(e.key, e.value); } }

abstract class HashIterator { int nextSegmentIndex; int nextTableIndex; HashEntry[] currentTable; HashEntry nextEntry; HashEntry lastReturned;

HashIterator() {
    nextSegmentIndex = segments.length - 1;
    nextTableIndex = -1;
    advance();
}

final void advance() {
    if (nextEntry != null && (nextEntry = nextEntry.next) != null)
        return;

    while (nextTableIndex >= 0) {
        if ((nextEntry = entryAt(currentTable, nextTableIndex--)) != null)
            return;
    }

    while (nextSegmentIndex >= 0) {
        Segment<K,V> seg = segmentAt(segments, nextSegmentIndex--);
        if (seg != null && (currentTable = seg.table) != null
推荐阅读:
  1. Hashtable、ConcurrentHashMap源码分析
  2. Java8 ConcurrentHashMap源码中隐藏的两个Bug是什么

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java concurrenthashmap

上一篇:python实现单例的方法有哪些

下一篇:MySQL子查询语句怎么使用

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》