jdk学习——ConcurrentHashMap

前言

在HashMap的基础上,继续学习ConcurrentHashMap,其利用到基本原理是,将哈希表中的锁竞争分段到每一个节点。而并不是将整个哈希表锁住。
在JDK8之前,ConcurrentHashMap 的实现是让每一个节点持有一个锁,然后当访问对应节点的时候,则使用这个锁只锁住一个节点而不是整个哈希表。这个理念与Innodb希望用行锁取代表锁提高并发量很类似。

	static class Segment<K,V> extends ReentrantLock implements Serializable {
   
        private static final long serialVersionUID = 2249069246763182397L;
        final float loadFactor;
        Segment(float lf) {
    this.loadFactor = lf; }
    }

可以看出这个对象实现了 ReentrantLock,那么其本身就是一把锁。
不过在JDK8及其之后,由于JVM本身就对于 synchronized 进行了锁优化,有可以从锁消除,偏向锁,轻量级锁,重量级锁升级过程,在没有竞争的场景下,偏向锁和轻量级锁的性能已经与 ReentrantLock 差不多甚至可能会更好,那么于是jdk8源码中就直接使用 synchronized 来取代了 Segment 的设计

一、数据结构

学习了HashMap之后,可以知道 ConcurrentHashMap 的底层数据结构也是一个数组。
其结构与HashMap的数据结构几乎一样,甚至我会以为代码就是copy过来的。

    static class Node<K,V> implements Map.Entry<K,V> {
   
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
   }
    /**
     * The array of bins. Lazily initialized upon first insertion.
     * Size is always a power of two. Accessed directly by iterators.
     */
    transient volatile Node<K,V>[] table;
    /**
     * The next table to use; non-null only while resizing.
     */
    private transient volatile Node<K,V>[] nextTable;

但是在数据的结构上面有一点区别,区别在于现在底层的数组表时由两个数组构成而不是一个。原因在于ConcurrentHashMap 是一个并发支持的数据结构,可同时支持数据的update和query,那么如果有多个线程同时对ConcurrentHashMap进行插入和查找,并且在这时发生了rehash,那么其实如果只有一个表,query是会查询到脏数据。
正在进行rehash的表如果这个我们访问1,4时,应该使用原来的索引还是新的索引呢?

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;
        }
}
static final int MOVED     = -1; // hash for forwarding nodes

一个特殊节点,当正在发生rehash时,会通过这个节点替换原有的节点,并通过这个节点可以指向新的哈希表节点。
前向节点的哈希值固定为-1。

	static final class ReservationNode<K,V> extends Node<K,V> {
   
        ReservationNode() {
   
            super(RESERVED, null, null, null);
        }

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

一个特殊节点,在使用compute函数时的占位节点,固定哈希值为-3

    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
    }

一个红黑树的出口节点,这个节点会持有红黑树的root。并且会在这里进行一些红黑树的无锁并发操作。

二、基本常量

    /**
     * Base counter value, used mainly when there is no contention,
     * but also as a fallback during table initialization
     * races. Updated via CAS.
     */
    private transient volatile long baseCount;
    /**
     * Table initialization and resizing control.  When negative, the
     * table is being initialized or resized: -1 for initialization,
     * else -(1 + the number of active resizing threads).  Otherwise,
     * when table is null, holds the initial table size to use upon
     * creation, or 0 for default. After initialization, holds the
     * next element count value upon which to resize the table.
     */
    private transient volatile int sizeCtl;
    /**
     * The next table index (plus one) to split while resizing.
     */
    private transient volatile int transferIndex;
    /**
     * Table of counter cells. When non-null, size is a power of 2.
     */
    private transient volatile CounterCell[] counterCells;
  • baseCount是哈希表中一个基本的计数的变量,哈希表在没有竞争的时候,会将递增的数字写入到baseCount中,但是如果在写入的时候发生了竞争关系,那么其实就不会将增长的数量写入到 baseCount 而是使用其他的策略写入
  • sizeCtl 是在哈希表中的尺寸控制,如果在哈希表中处于正常的操作状态,那么sizeCtl就是哈希表的容量,而如果哈希表为负值,那么就表明当前的哈希表正在进行扩容阶段。高16位会保存rehash的一个校验指纹,而低16位会保存当前正在rehash的线程数
  • transferIndex 是哈希表在扩容阶段时将要rehash的游标,每个线程会从这个游标上获取一个范围
  • counterCells 是用于并发竞争场景下,计算哈希表节点数量的一个额外数据结构,如果在数量增长的时候未发生竞争,那么就跟这个变量没有关系,但是如果发生了竞争,则会将增长的数量随机写入到一个CounterCell中去,随机写入到一个CounterCell的逻辑其实也是类似于一个分段锁的概念

三、基本操作

1.get查询

    public V get(Object key) {
   
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        // 通过 spread 函数将哈希码的高位扩散到低位,与HashMap的计算方式很类似
        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.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值