2019独角兽企业重金招聘Python工程师标准>>>

注:分析JDK8的ConcurrentHashMap,JDK6/7上的实现和JDK8上的不一样。

1.构造方法中的参数含义

构造方法中,有三个参数,如下,第三个参数才是一位数组的长度,第一个参数和第二个参数与Map的扩容有关

List-1

public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)

构造方法如下List-2

List-2 initialCapacity的值不能小于concurrencyLevel,通过initialCapacity和loadFactor计算出值并赋值给sizeCtl

    public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();if (initialCapacity < concurrencyLevel)   // Use at least as many binsinitialCapacity = concurrencyLevel;   // as estimated threadslong size = (long)(1.0 + (long)initialCapacity / loadFactor);int cap = (size >= (long)MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : tableSizeFor((int)size);this.sizeCtl = cap;}

sizeCtl在哪用到了,如下图1所示,如果sizeCtl的值大于0,说明我们指定了capacity,所以不会使用默认的DEFAULT_CAPACITY。注意sizeCtl的值是容量,不是当前Map中元素的个数。

图1 初始化表

2.并发度,一维数组

一维数组存储在这个中,长度默认是16,即默认的ConcurrenyLevel。

List-2

transient volatile Node<K,V>[] table=(Node<K,V>[])new Node<?,?>[n];

Node的类结构如下所示:

List-3

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

来看张ConcurrentHashMap的数据结构图,如下图1所示:

图1 ConcurrentHashMap的数据结构

如图1所示,当A对应的链表长度达到8后,就会转换为红黑树。为什么是8:属性TREEIFY_THRESHOLD上有注释,不过没怎么看懂。我个人觉得,如果链表长度小于8就转换为红黑树,效率上可能不如链表,毕竟红黑树的调整是比链表复杂的,这时不如直接使用链表;如果链表长度超过8,那么使用链表,查询效率会变低(最坏情况下遍历整条链表),此时用红黑树那么就能将查询的复杂度由O(n)降低到LogN。

红黑树中的节点TreeNode:

List-4

static final class TreeNode<K,V> extends Node<K,V> {TreeNode<K,V> parent;  // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev;    // needed to unlink next upon deletionboolean red;TreeNode(int hash, K key, V val, Node<K,V> next,TreeNode<K,V> parent) {super(hash, key, val, next);this.parent = parent;}
...
}

3.多线程进行put,如何保证线程安全

来看一张图,如下图2:

图2 putVal方法中

假设put(x)时,插入的bucket位置是图1中A对应的链表,那么会对A进行synchronized,这样就保证了同一时刻只有线程能访问synchronized的代码块,这个代码块中,有插入Node的操作,这样就保证了线程安全。

ConcurrentHashMap的get()方法是不需要加上synchronize的,因为很多关键的属性都有volatile修饰保证了可见性,所以get()方法上不加synchronize的情况下,也可以得到结果。

4.如何扩容的,什么时候扩容

问题1:每次进行put后,都会调用addCount方法,对Map中当前元素数量加1。之后会调用sumCount()方法获取Map中当前有多少个元素,如果等于或者大于sizeCtl的值,就会触发扩容。

如下这个方法中就是触发扩容的入口。

addCount(long x, int check)

如下这个属性与扩容有关。

private transient volatile Node<K,V>[] nextTable;

实现扩容的代码在如下方法中,实现起来很复杂,可以参考这篇文章。

transfer(Node<K,V>[] tab, Node<K,V>[] nextTab)

5.怎么实现统计Map中元素个数的功能

与俩个属性有关,如下:

    /*** 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 of counter cells. When non-null, size is a power of 2.*/private transient volatile CounterCell[] counterCells;

注:counterCells的长度与CPU个数有关。

CounterCell类如下:

@sun.misc.Contended static final class CounterCell {volatile long value;CounterCell(long x) { value = x; }}

解说下这个baseCount和counterCells是如何工作的:

  • 情况1: 假设只有一个线程进行put,Map中元素个数加1,会对baseCount进行CAS进行值加1,由于此时只有一个线程,所以CAS会成功,注意这里的是Unsafe的CAS,而不是AtomicInteger之类的CAS,所以如果失败会马上返回false。
  • 情况2:假设有多个线程进行put,此时多个线程对baseCount进行CAS,此时若有些线程发生CAS失败,说明对baseCount竞争失败,注意这里使用的是Unsafe的CAS,而不是AtomicInteger之类的CAS,所以如果失败会马上返回false。此时从counterCells中随机选取一个出来,对CounterCell的value做CAS进行加1,如果此时发生CAS失败,说明碰到了竞争,怎么处理呢,看下图,将counterCells的数组长度翻倍,之后拷贝元素组的值到新数组中对应的位置。

调用size()时,会调用sumCount(),sumCount()方法实现如下:

    final long sumCount() {CounterCell[] as = counterCells; CounterCell a;long sum = baseCount;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;}

首先获得baseCount的值,之后遍历每个counterCells中的value,就是Map中的元素的值,当然,得到的这个值不一定准确、不是实时的。

JDK8的ConcurrentHashMap中计算Map中元素个数的方法与LongAddr、DoubleAdder很类似。

JDK8中获取size的实现,比JDK6/7中的要好很多了,如果你看过JDK6/7中ConcurrentHashMap的实现,应该会有所感受的

转载于:https://my.oschina.net/u/2518341/blog/1841714

JDK之ConcurrentHashMap相关推荐

  1. 【Java自顶向下】试手小公司,面试官问我ConcurrentHashMap,我微微一笑……

    文章目录 ConcurrentHashMap 一.ConcurrentHashMap初始化的剖析 1.1 ConcurrentHashMap初始化 1.2 理解sizeCtl 二.JDK8的添加安全 ...

  2. Java并发编程-ConcurrentHashMap

    目录 1. JDK 7 HashMap 并发死链 1.1.HashMap回顾 1.2.测试代码 1.3.死链复现 1.4.源码复现 1.5.小结 2. JDK 8 ConcurrentHashMap ...

  3. HashMap与ConcurrentHashMap万字源码分析

    HashMap与ConcurrentHashMap`源码解析 JDK版本:1.7 & 1.8 ​ 开发中常见的数据结构有三种: 1.数组结构:存储区间连续.内存占用严重.空间复杂度大 优点:因 ...

  4. 想进大厂?50个多线程面试题,你会多少?(一)

    最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案. 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让 ...

  5. 万字详解本地缓存之王 Caffeine

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 来自:r6d.cn/UXR4 概要 Caffeine[1] ...

  6. 是什么让 Spring5 放弃了使用 Guava Cache?

    来源:https://albenw.github.io/posts/a4ae1aa2/ 概要 Caffeine是一个高性能,高命中率,低内存占用,near optimal 的本地缓存,简单来说它是Gu ...

  7. SpringBoot2.x 官方推荐缓存框架-Caffeine高性能设计剖析

    概要 Caffeine是一个高性能,高命中率,低内存占用,near optimal 的本地缓存,简单来说它是Guava Cache的优化加强版,有些文章把Caffeine称为"新一代的缓存& ...

  8. 据说是“缓存之王”? Caffeine高性能设计剖析

    概要 Caffeine[1]是一个高性能,高命中率,低内存占用,near optimal 的本地缓存,简单来说它是 Guava Cache 的优化加强版,有些文章把 Caffeine 称为" ...

  9. 高并发读,高并发写解决方案

    高并发读 动静分离与CDN加速 网站开发分为静态内容和动态内容两部分. 1. 静态内容:常用的处理测了就是CDN,一个静态文件缓存到网络各个节点. 加缓存 当数据库支持不住时,首先想到的是加一层缓存, ...

最新文章

  1. [国家集训队]数颜色 / 维护队列 (带修莫队模板题)
  2. pandas 判断数据表中的缺失值
  3. sTC8G1K08+通过串口显示内部电压_基于51单片机的数字电流电压表
  4. Fiddle:使用断点:bpu,bpafter
  5. amqp rabbitmq_通过Spring Integration和RabbitMQ获得高可用性的AMQP支持的消息通道
  6. 指针和引用的区别和联系
  7. html的语义化面试题,前端面试题-HTML结构语义化
  8. Python Pandas –操作
  9. Linux日志服务器的搭建
  10. 前沿 | 使用Transformers进行端到端目标识别
  11. Oracle VM + Windows2003 Server 配置
  12. 巧妙布局的APP界面模板,让你的作品更有吸引力
  13. 用自动化构建工具增强你的工作流程——gulp
  14. 《linux c编程指南》学习手记1
  15. linux 执行脚本报错:nginx_check.sh:行10: 语法错误: 未预期的文件结尾
  16. php array 删除末尾,PHP array_pop():删除数组末尾的元素
  17. Linux下rpm安装lrzsz
  18. aiml简介+源代码解析+中文分词(java)
  19. flutter 一键生成安卓和ios应用图标
  20. 【文献阅读】Cost-Sensitive Portfolio Selection via Deep Reinforcement Learning

热门文章

  1. 中国镍氢电池行业产销状况及竞争格局咨询报告2021-2027年版
  2. Windows Server 2008 R2 下配置证书服务器和HTTPS方式访问网站
  3. python配置核_浅谈pytorch卷积核大小的设置对全连接神经元的影响
  4. 打造微量元素产业-丰收节交易会·李喜贵:签约南国健康产业
  5. js判断鼠标旋转度数以及顺逆方向详解
  6. DBUtils (30)
  7. 监控:系统构架重要的一环
  8. shell基本语法和执行
  9. Day8 - Python网络编程 Socket编程 --转自金角大王
  10. 多线程的操作与数据绑定