ConcurrentHashMap底层源码详解及原理

本文详细分析了Java中的并发容器ConcurrentHashMap的内部实现,包括其Node结构、属性如sizeCtl、table、nextTable等,以及关键方法如get、put的实现细节。ConcurrentHashMap通过线程安全的数据结构和操作确保在多线程环境下的安全性,并在高并发下提供高效性能。其在初始化、扩容以及节点查找和插入过程中,利用了CAS和锁等机制保证了并发控制。
摘要由CSDN通过智能技术生成

HashMap在多线程中并不安全,为了解决HashMap的并发安全性,可以使用

1、Collections.synchronizedMap()方法将普通HashMap变为线程安全的

2、更高效的做法是使用Java自带的ConcurrentHashMap类

ConcurrentHashMap的用法这里就不多讲,与HashMap用法相似,只是添加了线程安全机制

一、ConcurrentHashMap属性分析

static class Node<K,V> implements Map.Entry<K,V>; 

整个类就是一个Node[],也就是每个HashMap存储数据的单元就是一个Node

private transient volatile int sizeCtl;//默认为0,初始化时为-1

当初始化或扩容完成时,作为下一个扩容阀值的大小

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

这个就是我们的哈希表

private transient volatile Node<K,V>[] nextTable;

这个是我们扩容时新的哈希表

static final class ForwardingNode<K,V> extends Node<K,V> 

这个代表如果某个bin迁移完毕,用该类代表迁移完毕bin的头结点。

简单来说,就是当我们遇到扩容时,某个哈希节点的所有的值已经从旧哈希表上转移到了新的表上,此时线程就会将该节点的头结点转变为ForwardingNode,这样的话当下一个线程试图转移该节点时就会失败,保证节点转移的线程安全

static final class TreeBin<K,V> extends Node<K,V>

当我们的链表个数达到阀值以后,就会将链表转化为红黑树(同HashMap),该类代表的就是红黑树的头结点

static final class TreeNode<K,V> extends Node<K,V> 

该类代表红黑树的普通节点

二、方法分析

public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (initialCapacity < concurrencyLevel)   // Use at least as many bins
            initialCapacity = concurrencyLevel;   // as estimated threads
        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        int cap = (size >= (long)MAXIMUM_CAPACITY) ?
            MAXIMUM_CAPACITY : tableSizeFor((int)size);
        this.sizeCtl = cap;
    }

构造方法

initialCapacity:初始化哈希表的大小的数值

loadFactor:负载因子,就是我们说的0.75,也就是当当前的存储数据的大小为当前容量大小的0.75倍时扩容

concurrencyLevel:就是并发操作当前Map的线程数

1、我们注意到,当初始化容量要小于线程数量的时候,我们会把初始化容量与线程数等值(至少做到一个县城能操作一个哈希节点)。

2、我们同时还能看到,我们初始化进去的容量大小不一定是哈希表的大小,实际大小需要经过计算得来。

3、jdk8中的ConcurrentHashMap实现了懒加载,就是在使用时才会创建哈希表

get

public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

1、首先我们会调用spread方法讲求到的哈希值转变为正数(在ConcurrentHashMap中默认正数的哈希值是有效的)

2、接下来我们回去检查哈希表中该哈希值对相应的节点下是否有链表(在这之前会先做哈希扰动),如果有,则比对哈希值并返回

3、如果没有(eh<0)则由两种情况,第一种情况是该链表处于移动过程,呢我们就会调用方法去新的哈希表中找该节点;第二种情况是该处已经由链表变为了红黑树,则我们掉用树的查找方式查找

put

public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

(相当长的代码)

1、首先同HashMap一样,我们会先求出Key的哈希值(中间一系列操作比如spread和哈希扰动),求出来后锁定相应哈希表的位置

2、如果此时该位置没有链表,我们就直接创建节点加入

3、如果该处有链表,我们就将搜索链表,如果遇到相同的Key值,我们就将新的value值替换就得value值

4、如果此时该链表处于移动的状态,比如线程1在移动链表,线程2在插入节点,此时线程2会帮助线程1移动链表,也就是将该链表加锁,加锁后该链表只能继续移动,不能被其他线程修改

5、如果此时在链表中没有找到Key值相同的节点,我们就会将该节点插入链表

6、如果此时链表已经变成了红黑树,我们就会调用TreeBin方法将该节点包装成树节点

注意: 

1、在整个过程中binCount值用来计数,判断链表是否需要升级成红黑树

2、我们之前提到 ConcurrentHashMap是懒加载,当我们插入时如果发现还未创建哈希表,我们就会调用Inittable方法创建哈希表(该方法底层是cas方法,保证只创建一个哈希表)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值