ConcurreentHashMap的实现原理与使用

ConcurrentHashMap是线程安全且高效的HashMap。

为什么要使用ConcurrentHashMap

在并发编程中使用HashMap可能导致程序死循环。而使用线程安全的HashTable效率又非常低下,基于以上两个原因,便有了ConcurrentHashMap的登场机会。

  1. 线程不安全的HashMap

    在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

    HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,,就会产生死循环获取Entry。

  2. 效率低下的HashTable

    HashTable容器使用了synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞或轮询状态。

  3. ConcurrentHashMap的锁分段技术可有效提升并发访问率

    容器里有很多把锁,每一把锁用于锁容器中其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。

ConcurrentHashMap的结构

ConcurrentHashMap是由Segment数组结构和HashEntry数据结构组成。Segment是一种可重入锁(ReentrantLock),在ConcurrentHashMap里扮演锁的角色;HashEntry则用于存储键值对数据。

ConcurrentHashMap的操作

  1. get操作

    Segment的get操作实现非常简单和高效。先经过一次再散列,然后使用这个散列值通过散列运算定位到Segment,再通过散列算法定位到元素:

     public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    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.equals(ek)))
    return e.val;
    }
    else if (eh < 0)
    return (p = e.find(h, key)) != null ? p.val : null;
    while ((e = e.next) != null) {
    if (e.hash == h &&
    ((ek = e.key) == key || (ek != null && key.equals(ek))))
    return e.val;
    }
    }
    return null;
    }
  2. put操作

    由于put操作方法里需要对共享变量进行写操作,所以为了线程安全,在操作共享变量时必须加锁。

    public V put(K key, V value) {
    return putVal(key, value, false);
    }
    final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
    Node<K,V> f; int n, i, fh;
    if (tab == null || (n = tab.length) == 0)
    tab = initTable();
    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) {
    if (tabAt(tab, i) == f) {
    if (fh >= 0) {
    binCount = 1;
    for (Node<K,V> e = f;; ++binCount) {
    K ek;
    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;
    if ((e = e.next) == null) {
    pred.next = new Node<K,V>(hash, key,
    value, null);
    break;
    }
    }
    }
    else if (f instanceof TreeBin) {
    Node<K,V> p;
    binCount = 2;
    if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
    value)) != null) {
    oldVal = p.val;
    if (!onlyIfAbsent)
    p.val = value;
    }
    }
    }
    }
    if (binCount != 0) {
    if (binCount >= TREEIFY_THRESHOLD)
    treeifyBin(tab, i);
    if (oldVal != null)
    return oldVal;
    break;
    }
    }
    }
    addCount(1L, binCount);
    return null;
    }

    (1)是否需要扩容

    在插入元素前先会先判断Segment里的HashEntry数组是否超过容量(threshold),如果超过阈值,则对数组进行扩容

    (2)如何扩容

    在扩容的时候,首先会创建一个容量是原来容量两倍的数组,然后对原数组里的元素进行再散列后插入到新的数组里。为了高效,ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容。

  3. size操作

    如果要统计整个ConcurrentHashMap里元素的大小,就必须统计所有Segment里的元素的大小后求和。

    public int size() {
    long n = sumCount();
    return ((n < 0L) ? 0 :
    (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
    (int)n);
    }

并发容器——ConcurrentHashMap的更多相关文章

  1. 计算机程序的思维逻辑 (74) - 并发容器 - ConcurrentHashMap

    本节介绍一个常用的并发容器 - ConcurrentHashMap,它是HashMap的并发版本,与HashMap相比,它有如下特点: 并发安全 直接支持一些原子复合操作 支持高并发.读操作完全并行. ...

  2. Java编程的逻辑 (74) - 并发容器 - ConcurrentHashMap

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  3. JAVA 多线程随笔 (三) 多线程用到的并发容器 (ConcurrentHashMap,CopyOnWriteArrayList, CopyOnWriteArraySet)

    1.引言 在多线程的环境中,如果想要使用容器类,就需要注意所使用的容器类是否是线程安全的.在最早开始,人们一般都在使用同步容器(Vector,HashTable),其基本的原理,就是针对容器的每一个操 ...

  4. Java并发编程:并发容器ConcurrentHashMap

    Java并发编程:并发容器之ConcurrentHashMap(转载) 下面这部分内容转载自: http://www.haogongju.net/art/2350374 JDK5中添加了新的concu ...

  5. 多线程之并发容器ConcurrentHashMap

    这部分内容转载自: http://www.haogongju.net/art/2350374 JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步 ...

  6. 并发容器ConcurrentHashMap#put方法解析

    jdk1.7.0_79 HashMap可以说是每个Java程序员用的最多的数据结构之一了,无处不见它的身影.关于HashMap,通常也能说出它不是线程安全的.这篇文章要提到的是在多线程并发环境下的Ha ...

  7. 8.并发容器ConcurrentHashMap#put方法解析

    jdk1.7.0_79 HashMap可以说是每个Java程序员用的最多的数据结构之一了,无处不见它的身影.关于HashMap,通常也能说出它不是线程安全的.这篇文章要提到的是在多线程并发环境下的Ha ...

  8. 多线程之并发容器ConcurrentHashMap(JDK1.6)

    简介 ConcurrentHashMap 是 util.concurrent 包的重要成员.本文将结合 Java 内存模型,分析 JDK 源代码,探索 ConcurrentHashMap 高并发的具体 ...

  9. 并发容器-ConcurrentHashMap,CopyOnWriteArrayList

    ConcurrentHashMap HashMap是线程非安全的,在多线程环境下,采用的是Fail-Fast快速失败机制,即当A线程在访问容器的时候,如果此时B线程修改了HashMap的结构,那么就会 ...

随机推荐

  1. [Oacle][Partition]Partition操作与 Index, Global Index 的关系

    [Oacle][Partition]Partition操作与 Index, Global Index 的关系: ■ Regarding the local index and the global i ...

  2. Combobox值自定义(不通过数据库)

    前台 <ext:ComboBox ID="cmbYear" runat="server"/> .aspx.cs int year = DateTim ...

  3. SERDES关键技术总结

    转自https://www.cnblogs.com/liujinggang/p/10125727.html 一.SERDES介绍 随着大数据的兴起以及信息技术的快速发展,数据传输对总线带宽的要求越来越 ...

  4. 总目录(Catalog)

    总目录(Catalog) C#高级编程(C# advanced programming) 1.并发编程(Concurrent programming)(8) ...... 数据结构与算法(Data s ...

  5. item 5: 比起显式的类型声明,更偏爱auto

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 啊,简单愉快的代码: int x; 等等,讨厌!我忘了初始化x,所 ...

  6. LESS SCSS 预编译语言

      前  言 JRedu LESS  是一门 CSS 预处理语言,它扩展了 CSS 语言,增加了变量.Mixin.函数等特性,使 CSS 更易维护和扩展. LESS 为 Web 开发者带来了福音,它在 ...

  7. 一个高性能的对象属性复制类,支持不同类型对象间复制,支持Nullable<T>类型属性

    由于在实际应用中,需要对大量的对象属性进行复制,原来的方法是通过反射实现,在量大了以后,反射的性能问题就凸显出来了,必须用Emit来实现. 搜了一圈代码,没发现适合的,要么只能在相同类型对象间复制,要 ...

  8. react-创建react元素

    前言 react 元素,即JSX语法. const Nav, Profile; // 输入(JSX): const app = <Nav color="blue">&l ...

  9. Week2 关于代码规范的一些认识

    代码规范 我觉得代码规范是有必要的,而对于以下的四个观点我要提出自己的反驳: 这些规范都是官僚制度下产生的浪费大家的编程时间.影响人们开发效率, 浪费时间的东西 首先应该明白,什么是“编码规范”?它不 ...

  10. Maven的课堂笔记3

    8 仓库管理 仓库可以分为三种:1.本地仓库(本机).2.私服(公司局域网内的maven服务器).3.中央仓库(互联上,例如 struts2官网,或者hibernate官网) 可以根据maven坐标定 ...