数据结构解析-ConcurrentHashMap

概要

我们在上一篇对HashTable进行了原理解析,再加上之前我们对HashMap的原理解析,这篇对ConcurrentHashMap的解析可称为数据结构解析三步曲了。

在这里我们也来总结一下他们的特点:
  • HashMap采用数组+链表+红黑树方式实现,提高查找效率;它永远长度是2的整倍数,可能出现hash冲突造成get出现死循环,在多线程下无法保证数据安全。
  • HashTable采用数组+链表的方式实现,采用Synchronized关键字锁方法的方式锁住了整个table来保证在多线程下的数据安全,但这样无疑使执行效率变得低下
  • ConcurrentHashMap采用数组+链表+红黑树的方式实现,采用Synchronized关键字实现分段锁技术降低锁的颗粒度,利用多个锁控制多个私有的小table,来保证多线程下的数据安全,提高执行效率.

让你能清晰的了解三者的区别:

散列表 实现方式 数据安全 数据安全实现方式 key\value是否可为Null
HashMap 数组+单向链表+红黑树 不安全 可为Null
HashTable 数组+单向链表 安全 Synchronized 不可为 Null
ConcurrentHashMap 数组+单向链表+红黑树 安全 Synchronized实现分段锁技术 不可为 Null

ConcurrentHashMap

1.继承关系

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable

2.常量&构造方法

    //ConcurrentHashMap容量极限
    private static final int MAXIMUM_CAPACITY = 1 << 30;
    //ConcurrentHashMap容量初始大小
    private static final int DEFAULT_CAPACITY = 16;
    //数组容量极限
    static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //默认并发级别
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    //默认负载因子大小
    private static final float LOAD_FACTOR = 0.75f;
    //链表转红黑树的阈值
    static final int TREEIFY_THRESHOLD = 8;
    //节点数小于等6保持单向链表状态
    static final int UNTREEIFY_THRESHOLD = 6;
    //红黑树容量最小64
    static final int MIN_TREEIFY_CAPACITY = 64;
    //每个转移步骤的最小复归数
    private static final int MIN_TRANSFER_STRIDE = 16;
    private static final int RESIZE_STAMP_BITS = 16;
    //可以帮助调整大小的最大线程数
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
    //记录生成戳的偏移位
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
    //控制table初始化和扩容的标记量
    //-1时 表示 正在初始化或正在扩容
   //-(1+n) 表示 活动调整的线程数量
    private transient volatile int sizeCtl;

    //节点hash字段编码
    static final int MOVED     = -1; // 转发节点的hash
    static final int TREEBIN   = -2; // hash树的根
    static final int RESERVED  = -3; // 临时保留的hash
    static final int HASH_BITS = 0x7fffffff; //普通节点hash的可用位
 //默认构造方法
 public ConcurrentHashMap() {
 }
 //指定初始化容量大小
 public ConcurrentHashMap(int initialCapacity) {
        //若指定的容量小于0 则抛出IllegalArgumentException异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        //获取table容量
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }
 //指定初始容量大小 & 指定负载因子大小
 public ConcurrentHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, 1);
    }
 //指定初始容量大小 & 指定负载因子大小 & 估算更新线程的并发数目
 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;
    }
//传入一个Map集合,将Map集合中元素Map.Entry全部添加进HashMap实例中
 public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
        this.sizeCtl = DEFAULT_CAPACITY;
        putAll(m);
    }

3.Node单向链表的实现

    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 Helpers.mapEntryToString(key, val);
        }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        }

        /**
         * Virtualized support for map.get(); overridden in subclasses.
         */
        Node<K,V> find(int h, Object k) {
            Node<K,V> e = this;
            if (k != null) {
                do {
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                } while ((e = e.next) != null);
            }
            return null;
        }
    }

4.TreeNode红黑树实现

static final class TreeNode<K,V> extends Node<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;

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

        Node<K,V> find(int h, Object k) {
            return findTreeNode(h, k, null);
        }

        /**
         * Returns the TreeNode (or null if not found) for the given key
         * starting at given root.
         */
        final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
            if (k != null) {
                TreeNode<K,V> p = this;
                do {
                    int ph, dir; K pk; TreeNode<K,V> q;
                    TreeNode<K,V> pl = p.left, pr = p.right;
                    if ((ph = p.hash) > h)
                        p = pl;
                    else if (ph < h)
                        p = pr;
                    else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                        return p;
                    else if (pl == null)
                        p = pr;
                    else if (pr == null)
                        p = pl;
                    else if ((kc != null ||
                              (kc = comparableClassFor(k)) != null) &&
                             (dir = compareComparables(kc, k, pk)) != 0)
                        p = (dir < 0) ? pl : pr;
                    else if ((q = pr.findTreeNode(h, k, kc)) != null)
                        return q;
                    else
                        p = pl;
                } while (p != null);
            }
            return null;
        }
    }

5.spread的计算实现

将(XORs)较高的哈希值向较低的哈希值扩展(XORs),并强制执行将最高位置0

 static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
 }

6.ConcurrentHashMap put的源码实现

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

final V putVal(K key, V value, boolean onlyIfAbsent) {
        //key 和 value 不可为Null,若为null则将抛出 NullPointerException异常
        if (key == null || value == null) throw new NullPointerException();
        //计算hash值
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //判定tab为Null 或 长度为0
            if (tab == null || (n = tab.length) == 0)
                //初始化tab
                tab = initTable();
             //通过执行tabAt函数寻找tab索引下的Entry
            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) { //对tab中一个节点加锁,这个锁锁在Entry的根节点上的
                    if (tabAt(tab, i) == f) { //做判定 若tab中第i个元素 等于 f节点
                       //此判定若成功 说明f节点下是一个普通节点 即链表节点
                        if (fh >= 0) {
                            binCount = 1;
                             //遍历整个链表
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //判定若成功则修改节点下的value值
                                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;
                                 //若指向的下一个节点为null
                                if ((e = e.next) == null) {
                                    //new出一个新节点进行插入 此处即我们存储put进来的值的新索引位置
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        //若 f类型TreeBin 黑红树
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                             //执行putTreeValue方法进行添加或查询节点操作
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }
                if (binCount != 0) {
                     //当节点长度大于等于8时
                    if (binCount >= TREEIFY_THRESHOLD)
                        //将原来的链表存储方式转换为黑红树存储
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }
梳理以下ConcurrentHashTable put函数的执行过程
  • 1.key 和 value 不可为Null,若为null则将抛出 NullPointerException异常
  • 2.判定tab为Null 或 长度为0 判定成功则初始化table
  • 3.否则 通过执行tabAt函数寻找tab索引下的Entry若为空 则判定是否需要比较并进行交换
  • 4.若tabAt函数寻找tab索引下的Entry不为空 则判定f.hash是否为MOVE 若判定成功 则在调整tab大小过程中帮助转移
  • 5.1.若以上条件都不成立 则 对tab中一个节点加锁,这个锁锁在Entry的根节点上的
  • 5.1.1 做判定 若tab中第i个元素 等于 f节点 此判定若成功 说明f节点下是一个普通节点 即链表节点
  • 5.1.1.1 遍历整个链表,查找符合条件的节点修改节点下的值
  • 5.1.2 若 f类型TreeBin 黑红树 则执行putTreeValue方法进行添加或查询节点操作
  • 5.2 当节点长度大于等于8时 将原来的链表存储方式转换为黑红树存储
  • 6.添加到总数中,若容量小则进行扩容

7.ConcurrentHashMap get的源码实现

public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        //计算hash值
        int h = spread(key.hashCode());
         //table 不为null & table长度大于0 & table索引位置下的Entry不为null
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            //做判定Hash匹配成功
            if ((eh = e.hash) == h) {
                //key的匹配成功
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    //返回节点下的vlaue
                    return e.val;
            }
            else if (eh < 0)  //hash节点小于0
                 //使用find函数在单向链表中查找value
                return (p = e.find(h, key)) != null ? p.val : null;
             //通过while循环,遍历e下的子节点查找value
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

8.ConcurrentHashMap treeifyBin的源码实现

单向链表转为黑红树
private final void treeifyBin(Node<K,V>[] tab, int index) {
        Node<K,V> b; int n;
         //判定单向链表不为null
        if (tab != null) {
            //判定tab长度若小于黑红树最小长度
            if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
                //对黑红树进行扩容
                tryPresize(n << 1);
             //tab指定索引下数据不为null & hash值大于0 
             //这里将tab赋给b 原因是为下面Synchronized准备的
            else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
               //加锁 锁住了b 是为了降低锁的颗粒度
                synchronized (b) {
                    if (tabAt(tab, index) == b) {
                        TreeNode<K,V> hd = null, tl = null;
                         //遍历b下的节点
                        for (Node<K,V> e = b; e != null; e = e.next) {
                             //声明黑红树
                            TreeNode<K,V> p =
                                new TreeNode<K,V>(e.hash, e.key, e.val,
                                                  null, null);
                            
                            if ((p.prev = tl) == null)
                                //将上一个子节点设置成下一个节点的父节点
                                hd = p;
                            else
                                //设置下一个节点
                                tl.next = p;
                            //设置子节点
                            tl = p;
                        }
                        //将tab指定的索引设置进以hd为头部节点的TreeBin中
                        setTabAt(tab, index, new TreeBin<K,V>(hd));
                    }
                }
            }
        }
    }
梳理以下ConcurrentHashTable treeifyBin函数的执行过程
  • 1.判定黑红树是否需要扩容
    1. tab指定索引下数据不为null & hash值大于0,这里将tab赋给b 原因是为下面Synchronized准备的
  • 2.1加锁 锁住了b 是为了降低锁的颗粒度
  • 2.3 遍历b下的节点,新建黑红树节点
  • 2.4 将上一个子节点设置成下一个节点的父节点,设置下一个节点,设置子节点
  • 2.5 将tab指定的索引设置进以hd为头部节点的TreeBin中

9.ConcurrentHashMap initTable的源码实现

private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0) //判定是否有其他线程在进行初始化和扩容操作
                Thread.yield(); // 让出线程执行资源
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;//指定数组大小
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

This ALL! Thanks EveryBody!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容