ConcurrentHashMap 原理

一 概述

我们都知道 HashMap 不是线程安全的,所以在处理并发的时候会出现问题。

而 HashTable 虽然是线程安全的,但是是通过整个方法来加锁的方式,当一个线程在写操作的时候,另外的线程则不能进行读写。

而 ConcurrentHashMap 则可以支持并发的读写。跟 1.7 版本相比,1.8 版本又有了很大的变化,已经抛弃了 Segment 的概念,虽然源码里面还保留了,也只是为了兼容性的考虑。

二 ConcurrentHashMap 原理

在 ConcurrentHashMap 中通过一个 Node[] 数组来保存添加到 map 中的键值对,而在同一个数组位置是通过链表和红黑树的形式来保存的。但是这个数组只有在第一次添加元素的时候才会初始化,否则只是初始化一个 ConcurrentHashMap 对象的话,只是设定了一个 sizeCtl 变量,这个变量用来判断对象的一些状态和是否需要扩容,后面会详细解释。

第一次添加元素的时候,默认初始长度为 16,当往 map 中继续添加元素的时候,通过 hash 值跟数组长度取与来决定放在数组的哪个位置,如果出现放在同一个位置的时候,优先以链表的形式存放,在同一个位置的个数又达到了 8 个以上,如果数组的长度还小于 64 的时候,则会扩容数组。如果数组的长度大于等于 64 了的话,则会将该节点的链表转换成红黑树。

通过扩容数组的方式来把这些节点给分散开。然后将这些元素复制到扩容后的新的数组中,同一个链表中的元素通过 hash 值的数组长度位来区分,是放在原来的位置还是放到扩容的长度的相同位置去 。在扩容完成之后,如果某个节点的是树,同时现在该节点的个数又小于等于 6 个了,则会将该树转为链表。

取元素的时候,相对来说比较简单,通过计算 hash 来确定该元素在数组的哪个位置,然后在通过遍历链表或树来判断 key 和 key 的 hash,取出 value 值。

往 ConcurrentHashMap 中添加元素的时候,里面的数据以数组的形式存放的样子大概是这样的:
在这里插入图片描述
这个时候因为数组的长度才为16,则不会转化为树,而是会进行扩容。

扩容后数组大概是这样的:
在这里插入图片描述
需要注意的是,扩容之后的长度不是 32,扩容后的长度在后面细说。

如果数组扩张后长度达到 64 了,且继续在某个节点的后面添加元素达到 8 个以上的时候,则会出现转化为红黑树的情况。

转化之后大概是这样:
在这里插入图片描述

三 相关概念

3.1 重要属性

    private static final int MAXIMUM_CAPACITY = 1 << 30;
    private static final int DEFAULT_CAPACITY = 16;
    static final int TREEIFY_THRESHOLD = 8;
    static final int UNTREEIFY_THRESHOLD = 6;
    static final int MIN_TREEIFY_CAPACITY = 64;
    static final int MOVED     = -1; // 表示正在转移
    static final int TREEBIN   = -2; // 表示已经转换成树
    static final int RESERVED  = -3; // hash for transient reservations
    static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
    transient volatile Node<K,V>[] table; // 默认没初始化的数组,用来保存元素
    private transient volatile Node<K,V>[] nextTable; // 转移的时候用的数组
    /**
         * 用来控制表初始化和扩容的,默认值为0,当在初始化的时候指定了大小,
         * 这会将这个大小保存在 sizeCtl 中,大小为数组的 0.75
         * 当为负的时候,说明表正在初始化或扩张,
         *     -1表示初始化
         *     -(1+n) n:表示活动的扩张线程
         */
        private transient volatile int sizeCtl;

3.2 重要的类

Node,这是构成每个元素的基本类。

    static class Node<K,V> implements Map.Entry<K,V> {
   
   
            final int hash;    //key的hash值
            final K key;       //key
            volatile V val;    //value
            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(); }
        }

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;
            }
    }

TreeBin 用作树的头结点,只存储 root 和 first 节点,不存储节点的 key、value 值。

    static final class TreeBin<K,V> extends Node<K,V> {
   
   
            TreeNode<K,V> root;
            volatile TreeNode<K,V> first;
            volatile Thread waiter;
            volatile int lockState;
            // values for lockState
            static final int WRITER = 1; // set while holding write lock
            static final int WAITER = 2; // set when waiting for write lock
            static final int READER = 4; // increment value for setting read lock
    }

ForwardingNode 在转移的时候放在头部的节点,是一个空节点

    static final class ForwardingNode<K,V> extends Node<K,V> {
   
   
            final Node<K,V>[] nextTable;
            ForwardingNode(Node<K,V>[] tab) {
   
   
                super(MOVED, null, null, null);
                this.nextTable = tab;
            }
    }

3.3 重要方法

在 ConcurrentHashMap 中使用了 unSafe 方法,通过直接操作内存的方式来保证并发处理的安全性,使用的是硬件的安全机制。

    /*
         * 用来返回节点数组的指定位置的节点的原子操作
         */
        @SuppressWarnings("unchecked")
        static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
   
   
            return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
        }
        /*
         * cas原子操作,在指定位置设定值
         */
        static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                            Node<K,V> c, Node<K,V> v) {
   
   
            return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
        }
        /*
         * 原子操作,在指定位置设定值
         */
        static final <K
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值