JDK之ConcurrentHashMap
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相关推荐
- 【Java自顶向下】试手小公司,面试官问我ConcurrentHashMap,我微微一笑……
文章目录 ConcurrentHashMap 一.ConcurrentHashMap初始化的剖析 1.1 ConcurrentHashMap初始化 1.2 理解sizeCtl 二.JDK8的添加安全 ...
- Java并发编程-ConcurrentHashMap
目录 1. JDK 7 HashMap 并发死链 1.1.HashMap回顾 1.2.测试代码 1.3.死链复现 1.4.源码复现 1.5.小结 2. JDK 8 ConcurrentHashMap ...
- HashMap与ConcurrentHashMap万字源码分析
HashMap与ConcurrentHashMap`源码解析 JDK版本:1.7 & 1.8 开发中常见的数据结构有三种: 1.数组结构:存储区间连续.内存占用严重.空间复杂度大 优点:因 ...
- 想进大厂?50个多线程面试题,你会多少?(一)
最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案. 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让 ...
- 万字详解本地缓存之王 Caffeine
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 来自:r6d.cn/UXR4 概要 Caffeine[1] ...
- 是什么让 Spring5 放弃了使用 Guava Cache?
来源:https://albenw.github.io/posts/a4ae1aa2/ 概要 Caffeine是一个高性能,高命中率,低内存占用,near optimal 的本地缓存,简单来说它是Gu ...
- SpringBoot2.x 官方推荐缓存框架-Caffeine高性能设计剖析
概要 Caffeine是一个高性能,高命中率,低内存占用,near optimal 的本地缓存,简单来说它是Guava Cache的优化加强版,有些文章把Caffeine称为"新一代的缓存& ...
- 据说是“缓存之王”? Caffeine高性能设计剖析
概要 Caffeine[1]是一个高性能,高命中率,低内存占用,near optimal 的本地缓存,简单来说它是 Guava Cache 的优化加强版,有些文章把 Caffeine 称为" ...
- 高并发读,高并发写解决方案
高并发读 动静分离与CDN加速 网站开发分为静态内容和动态内容两部分. 1. 静态内容:常用的处理测了就是CDN,一个静态文件缓存到网络各个节点. 加缓存 当数据库支持不住时,首先想到的是加一层缓存, ...
最新文章
- [国家集训队]数颜色 / 维护队列 (带修莫队模板题)
- pandas 判断数据表中的缺失值
- sTC8G1K08+通过串口显示内部电压_基于51单片机的数字电流电压表
- Fiddle:使用断点:bpu,bpafter
- amqp rabbitmq_通过Spring Integration和RabbitMQ获得高可用性的AMQP支持的消息通道
- 指针和引用的区别和联系
- html的语义化面试题,前端面试题-HTML结构语义化
- Python Pandas –操作
- Linux日志服务器的搭建
- 前沿 | 使用Transformers进行端到端目标识别
- Oracle VM + Windows2003 Server 配置
- 巧妙布局的APP界面模板,让你的作品更有吸引力
- 用自动化构建工具增强你的工作流程——gulp
- 《linux c编程指南》学习手记1
- linux 执行脚本报错:nginx_check.sh:行10: 语法错误: 未预期的文件结尾
- php array 删除末尾,PHP array_pop():删除数组末尾的元素
- Linux下rpm安装lrzsz
- aiml简介+源代码解析+中文分词(java)
- flutter 一键生成安卓和ios应用图标
- 【文献阅读】Cost-Sensitive Portfolio Selection via Deep Reinforcement Learning