Java--ConcurrentHashMap

1.7 中采用Segment + HashEntry ,用的是的ReentrantLock
1.8 Node + CAS + Synchronized,Synchronized锁的是Node节点

JDK7的ConcurrentHashMap:
ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
这里写图片描述
Segment 数组默认是16,不可扩容;Segment 内部是由 数组+链表 组成的。
put操作:根据 hash 值很快就能找到相应的 Segment,之后找到Segment内部数组的位置(此部分需要加锁,如果不够就进行扩容)。
ConcurrentHashMap 初始化的时候会初始化第一个槽 segment[0],对于其他槽来说,在插入第一个值的时候进行初始化。
get操作:
1、计算 hash 值,找到 segment 数组中的具体位置,或我们前面用的“槽”
2、槽中也是一个数组,根据 hash 找到数组中具体的位置
3、到这里是链表了,顺着链表进行查找即可

size操作

  1. 第一种方案他会使用不加锁的模式去尝试多次计算ConcurrentHashMap的size,最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的
  2. 第二种方案是如果第一种方案不符合,他就会给每个Segment加上锁,然后计算ConcurrentHashMap的size返回
Unsafe 类
// 通过内存屏障保证有序性,再通过volatile保证将对指定地址的操作马上写入到共享到主存中,这样配合get时候每次拿到的值都是最新的
void sun::misc::Unsafe::putObjectVolatile (jobject obj, jlong offset, jobject value)
  {
  write_barrier ();// 写屏障
  volatile jobject *addr = (jobject *) ((char *) obj + offset);
  *addr = value;
  }

void sun::misc::Unsafe::putObject (jobject obj, jlong offset, jobject value)
  {
  jobject *addr = (jobject *) ((char *) obj + offset);
  *addr = value;
  }//用于和putObjectVolatile进行对比
// 返回前加入read,这个读屏障作用就是强制去读取主内存中读数据,确保读到的一定是最新的
jobject sun::misc::Unsafe::getObjectVolatile (jobject obj, jlong offset)
  {
  volatile jobject *addr = (jobject *) ((char *) obj + offset);
  jobject result = *addr;
  read_barrier ();// 读屏障
  return result;
  }

void sun::misc::Unsafe::putOrderedObject (jobject obj, jlong offset, jobject value)
  {
  volatile jobject *addr = (jobject *) ((char *) obj + offset);
  *addr = value;
  }

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

        /**
         * Sets next field with volatile write semantics.  (See above
         * about use of putOrderedObject.)
         */
        final void setNext(HashEntry<K,V> n) {
            UNSAFE.putOrderedObject(this, nextOffset, n);
        }

        // Unsafe mechanics
        static final sun.misc.Unsafe UNSAFE; //可以理解为一个指针
        static final long nextOffset;//偏移量,可以简单的理解为内存地址
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();//获取这个节点对应的内存指针
                Class k = HashEntry.class;//
                nextOffset = UNSAFE.objectFieldOffset
                    (k.getDeclaredField("next")); //获取当前节点的next节点对于当前节点指针的偏移量
                    //通过UNSAFE中有方法直接能够获取到当前引用变量的初始内存地址
                    //通过初始内存地址和引用变量内部的局部变量的偏移量就可以通过Unsafe直接读取到对应的参数值
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

参考链接
https://blog.csdn.net/klordy_123/article/details/82933115

Java8 ConcurrentHashMap
这里写图片描述
底层依然由 Node数组+链表+红黑树 来实现的;
ConcurrentHashMap 使用 CAS 和 Synchronized 来保证线程安全。

put方法:

  1. 如果没有初始化就先调用initTable()方法来进行初始化过程
  2. 如果没有hash冲突就直接CAS插入
  3. 如果还在进行扩容操作就先进行扩容
  4. 如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接 遍历到尾端插入,一种是红黑树就按照红黑树结构插入,
  5. 最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环
  6. 如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容

get 方法:
计算 hash 值
根据 hash 值找到数组对应位置: (n – 1) & h
根据该位置处结点性质进行相应查找:
    如果该位置为 null,那么直接返回 null 就可以了
    如果该位置处的节点刚好就是我们需要的,返回该节点的值即可
    如果该位置节点的 hash 值小于 0,说明正在扩容,或者是红黑树,后面我们再介绍 find 方法
    如果以上 3 条都不满足,那就是链表,进行遍历比对即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值