此文已由作者赵计刚授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


4、get(Object key)

使用方法:

map.get("hello");

源代码:

ConcurrentHashMap的get(Object key)

    /**
     * 根据key获取value
     * 步骤:
     * 1)根据key获取hash值
     * 2)根据hash值找到相应的Segment
     * 调用Segment的get(Object key, int hash)
     * 3)根据hash值找出HashEntry数组中的索引index,并返回HashEntry[index]
     * 4)遍历整个HashEntry[index]链表,找出hash和key与给定参数相等的HashEntry,例如e,
     * 4.1)如没找到e,返回null
     * 4.2)如找到e,获取e.value
     * 4.2.1)如果e.value!=null,直接返回
     * 4.2.2)如果e.value==null,则先加锁,等并发的put操作将value设置成功后,再返回value值
     */
    public V get(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).get(key, hash);
    }

Segment的get(Object key, int hash)

        /**
         * 根据key和hash值获取value
         */
        V get(Object key, int hash) {
            if (count != 0) { // read-volatile
                HashEntry<K, V> e = getFirst(hash);//找到HashEntry[index]
                while (e != null) {//遍历整个链表
                    if (e.hash == hash && key.equals(e.key)) {
                        V v = e.value;
                        if (v != null)
                            return v;
                        /*
                         * 如果V等于null,有可能是当下的这个HashEntry刚刚被创建,value属性还没有设置成功,
                         * 这时候我们读到是该HashEntry的value的默认值null,所以这里加锁,等待put结束后,返回value值
                         */
                        return readValueUnderLock(e); 
                    }
                    e = e.next;
                }
            }
            return null;
        }

Segment的getFirst(int hash)

        /**
         * 根据hash值找出HashEntry数组中的索引index,并返回HashEntry[index]
         */
        HashEntry<K, V> getFirst(int hash) {
            HashEntry<K, V>[] tab = table;
            return tab[hash & (tab.length - 1)];
        }

Segment的readValueUnderLock(HashEntry<K, V> e)

        V readValueUnderLock(HashEntry<K, V> e) {
            lock();
            try {
                return e.value;
            } finally {
                unlock();
            }
        }

注意点:

  • 注释很重要,一定要看

  • 注释已经写明了详细流程,这里说一下大致流程:

    • 如没找到e,返回null

    • 如找到e,获取e.value

    • 如果e.value!=null,直接返回

    • 如果e.value==null,则先加锁,等并发的put操作将value设置成功后,再返回value值

    • 根据key获取hash值

    • 根据hash值找到相应的Segment

    • 根据hash值找出Segment中的哪一个HashEntry[index]

    • 遍历整个HashEntry[index]链表,找出hash和key与给定参数相等的HashEntry,例如e

  • 对于get操作而言,基本没有锁,只有当找到了e且e.value等于null,有可能是当下的这个HashEntry刚刚被创建,value属性还没有设置成功,这时候我们读到是该HashEntry的value的默认值null,所以这里加锁,等待put结束后,返回value值

  • 据说,上边这一点还没有发生过

5、remove(Object key)

使用方法:

map.remove("hello");

源代码:

ConcurrentHashMap的remove(Object key)

    /**
     * 删除指定key的元素
     * 步骤:
     * 1)根据key获取hash值
     * 2)根据hash值获取Segment
     * 调用Segment的remove(Object key, int hash, Object value)
     * 1)count-1
     * 2)获取将要删除的元素所在的HashEntry[index]
     * 3)遍历链表,
     * 3.1)若没有hash和key都与指定参数相同的节点e,返回null
     * 3.2)若有e,删除指定节点e,并将e之前的节点重新排序后,将排序后的最后一个节点的下一个节点指定为e的下一个节点
     * (很绕,不知道JDK为什么这样实现)
     */
    public V remove(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).remove(key, hash, null);
    }

Segment的remove(Object key, int hash, Object value)

        V remove(Object key, int hash, Object value) {
            lock();
            try {
                int c = count - 1;//key-value对个数-1
                HashEntry<K, V>[] tab = table;
                int index = hash & (tab.length - 1);
                HashEntry<K, V> first = tab[index];//获取将要删除的元素所在的HashEntry[index]
                HashEntry<K, V> e = first;
                //从头节点遍历到最后,若未找到相关的HashEntry,e==null,否则,有
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;                 V oldValue = null;
                if (e != null) {//将要删除的节点e
                    V v = e.value;
                    if (value == null || value.equals(v)) {
                        oldValue = v;
                        // All entries following removed node can stay
                        // in list, but all preceding ones need to be
                        // cloned.
                        ++modCount;
                        HashEntry<K, V> newFirst = e.next;
                        /*
                         * 从头结点遍历到e节点,这里将e节点删除了,但是删除节点e的前边的节点会倒序
                         * eg.原本的顺序:E3-->E2-->E1-->E0,删除E1节点后的顺序为:E2-->E3-->E0
                         * E1前的节点倒序排列了
                         */
                        for (HashEntry<K, V> p = first; p != e; p = p.next)
                            newFirst = new HashEntry<K, V>(p.key, p.hash, newFirst, p.value);
                        tab[index] = newFirst;
                        count = c; // write-volatile
                    }
                }
                return oldValue;
            } finally {
                unlock();
            }
        }

注意:具体的实现方式看注释,个人感觉比较绕,所以有兴趣的朋友可以按照如下步骤实现了一遍:(实现的过程可以参照HashMap的remove(Object key))

  • 根据key获取hash值

  • 根据hash值获取Segment

  • 获取将要删除的元素所在的HashEntry[index]

  • 遍历链表

    • 若没有hash和key都与指定参数相同的节点e,返回null

    • 若有e,删除指定节点e,并将e的前一个节点的next指向e的下一个节点,之后count-1

6、containsKey(Object key)

使用方法:

map.containsKey("hello")

源代码:

ConcurrentHashMap的containsKey(Object key)

    /**
     * 是否包含指定key的数据
     * 步骤:
     * 1)根据key计算hash值
     * 2)根据hash获取相应的Segment
     * 调用Segment的containsKey(Object key, int hash)
     * 3)根据hash值找出HashEntry数组中的索引index,并返回HashEntry[index]
     * 4)遍历整个HashEntry[index]链表,找出hash和key与给定参数相等的HashEntry,例如e,
     * 4.1)如找到e,返回true
     * 4.2)如没找到e,返回false
     */
    public boolean containsKey(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).containsKey(key, hash);
    }

Segment的containsKey(Object key, int hash)

        boolean containsKey(Object key, int hash) {
            if (count != 0) { // read-volatile
                HashEntry<K, V> e = getFirst(hash);
                while (e != null) {
                    if (e.hash == hash && key.equals(e.key))
                        return true;
                    e = e.next;
                }
            }
            return false;
        }

说明:代码清晰简单,流程步骤查看注释即可

7、keySet().iterator()

使用方法:

        Map<String, Object> map = new ConcurrentHashMap<String, Object>();
        map.put("hello3", "world2");
        map.put("hello2", "world");
        for(String key : map.keySet()){
            System.out.println(map.get(key));
        }

源代码不写了。

流程:

遍历每个Segment中的HashEntry[],完成所有对象的读取,不加锁。

8、size()

源代码:

    /**
     * 计算map中的key-value对总数
     * 步骤:
     * 1)遍历所有段,计算总的count值sum,计算总的modCount值
     * 2)如果有数据的话(modCount!=0),再遍历所有段一遍,计算总的count值check,在这期间只要有一个段的modCount发生了变化,就再重复如上动作两次
     * 3)若三次后,还未成功,遍历所有Segment,分别加锁(即建立全局锁),然后计算,最后释放所有锁
     */
    public int size() {
        final Segment<K, V>[] segments = this.segments;
        long sum = 0;//总量
        long check = 0;//标志位
        int[] mc = new int[segments.length];//存放每个段的modCount
        
        
        for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
            check = 0;
            sum = 0;//总的count值
            int mcsum = 0;//总的modCount值
            for (int i = 0; i < segments.length; ++i) {//遍历所有段
                sum += segments[i].count;//计算总的count值
                mcsum += mc[i] = segments[i].modCount;//计算总的modCount值
            }
            if (mcsum != 0) {//有数据的话,再检查一遍
                for (int i = 0; i < segments.length; ++i) {
                    check += segments[i].count;//计算总的count
                    if (mc[i] != segments[i].modCount) {//只要有一个段发生了变化(在遍历期间发生了增删变化)
                        check = -1; 
                        break;//跳出所有循环
                    }
                }
            }
            if (check == sum)//成功
                break;
        }
        if (check != sum) { //以上三次都为成功的话
            sum = 0;
            //每一个段全部加锁(相当于加了一个全局锁)
            for (int i = 0; i < segments.length; ++i)
                segments[i].lock();
            //进行统计
            for (int i = 0; i < segments.length; ++i)
                sum += segments[i].count;
            //全部解锁
            for (int i = 0; i < segments.length; ++i)
                segments[i].unlock();
        }
        if (sum > Integer.MAX_VALUE)
            return Integer.MAX_VALUE;
        else
            return (int) sum;
    }

在不加锁的情况下遍历所有Segment,读取每个Segment的count和modCount,并进行统计;

完毕后,再遍历一遍所有Segment,比较modCount,是否发生了变化,若发生了变化,则再重复如上动作两次;

若三次后,还未成功,遍历所有Segment,分别加锁(即建立全局锁),然后计算,最后释放所有锁。

注:以如上的方式,大部分情况下,不需要加锁就可以获取size()

总结:

  • 数据结构:一个指定个数的Segment数组,数组中的每一个元素Segment相当于一个HashTable

  • 加锁情况(分段锁):

    • put

    • get中找到了hash与key都与指定参数相同的HashEntry,但是value==null的情况

    • remove

    • size():三次尝试后,还未成功,遍历所有Segment,分别加锁(即建立全局锁)

jdk1.8 concurrentHashMap的实现

免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐

更多网易技术、产品、运营经验分享请点击

相关文章:
【推荐】 微服务化之无状态化与容器化
【推荐】 视觉设计师的进化
【推荐】 深入解读Service Mesh背后的技术细节

ConcurrentHashMap源码解析(3)的更多相关文章

  1. Java之ConcurrentHashMap源码解析

    ConcurrentHashMap源码解析 目录 ConcurrentHashMap源码解析 jdk8之前的实现原理 jdk8的实现原理 变量解释 初始化 初始化table put操作 hash算法 ...

  2. ConcurrentHashMap源码解析(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析. http:/ ...

  3. 第二章 ConcurrentHashMap源码解析

    注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析. http://www.cnblogs.com/java-zhao/p/5106189.html 1.对于 ...

  4. ConcurrentHashMap源码解析,多线程扩容

    前面一篇已经介绍过了 HashMap 的源码: HashMap源码解析.jdk7和8之后的区别.相关问题分析 HashMap并不是线程安全的,他就一个普通的容器,没有做相关的同步处理,因此线程不安全主 ...

  5. Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析

    目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...

  6. 数据结构算法 - ConcurrentHashMap 源码解析

    五个线程同时往 HashMap 中 put 数据会发生什么? ConcurrentHashMap 是怎么保证线程安全的? 在分析 HashMap 源码时还遗留这两个问题,这次我们站在 Java 多线程 ...

  7. 并发编程(十六)——java7 深入并发包 ConcurrentHashMap 源码解析

    以前写过介绍HashMap的文章,文中提到过HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容 ...

  8. 深入并发包 ConcurrentHashMap 源码解析

    以前写过介绍HashMap的文章,文中提到过HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容 ...

  9. ConcurrentHashMap源码解析 JDK8

    一.简介 上篇文章详细介绍了HashMap的源码及原理,本文趁热打铁继续分析ConcurrentHashMap的原理. 首先在看本文之前,希望对HashMap有一个详细的了解.不然看直接看Concur ...

  10. ConcurrentHashMap源码解析-Java7

    目录 一.ConcurrentHashMap的模型图 二.源码分析-类定义 2.1 极简ConcurrentHashMap定义 2.2 Segment内部类 2.3 HashEntry内部类 2.4 ...

随机推荐

  1. sql server profiler 工具使用 sql 语句性能分析

    https://www.cnblogs.com/knowledgesea/p/3683505.html

  2. Chrome firefox ie等浏览器空格(&nbsp;)兼容问题

    使用(&nbsp:)空格浏览器之间,显示的不一样,对不起等现象. 解决方案: 用半角空格&ensp:或者全角空格&emsp:就可以了,&ensp:相当于半格中文字符的宽 ...

  3. django 静态文件

    django中的静态文件,如图片,css样式jquery等等 在url最下面加上 from django.conf.urls.static import staticfrom django.conf ...

  4. 使用Simple MvvmToolkit开发Android和iOS程序

    详情见:Android and iOS Development with Simple MVVM Toolkit? Yes you can! :http://blog.tonysneed.com/20 ...

  5. keybd_event使用方法

    Windows提供了一个模拟键盘API函数Keybd_event(),使用该函数可以相应的屏蔽键盘的动作.Keybd_event()函数能触发一个按键事件,也就是说会产生一个WM_KEYDOWN或WM ...

  6. mybatis学习 十五 resultMap标签 一对多

    多次查询,非联合查询版本 <resultMap type="teacher" id="techMap"> <id column="i ...

  7. 使用SpringMVC的@CrossOrigin注解解决跨域请求问题

    跨域问题,通俗说就是用ajax请求其他站点的接口,浏览器默认是不允许的.同源策略(Same-orgin policy)限制了一个源(orgin)中加载脚本或脚本与来自其他源(orgin)中资源的交互方 ...

  8. 【RabbitMQ】三种类型交换器 Fanout,Direct,Topic(转)

    出处:https://blog.csdn.net/fxq8866/article/details/62049393 RabbitMQ服务器会根据路由键将消息从交换器路由到队列中,如何处理投递到多个队列 ...

  9. POSTMAN 数据关联

    概述 在使用postman测试接口是,我们可能需要先获取一个token,然后再将这个token发送到第二个请求.这个需要做postman的关联,一次性完成这两个测试. 实现方法 1.编写两个控制器方法 ...

  10. mysql order by 中文 排序

    mysql order by 中文 排序 1. 在MySQL中,我们经常会对一个字段进行排序查询,但进行中文排序和查找的时候,对汉字的排序和查找结果往往都是错误的. 这种情况在MySQL的很多版本中都 ...