ConcurrentHashMap(1.7)分析
1. 先来了解ConcurrentHashMap中的几个成员,当然大多数与HashMap中的相似,我们只看独有的成员
/**
* The default concurrency level for this table, used when not
* otherwise specified in a constructor.
*/
static final int DEFAULT_CONCURRENCY_LEVEL = 16; //默认的并发级别
/**
* The maximum capacity, used if a higher value is implicitly
* specified by either of the constructors with arguments. MUST
* be a power of two <= 1<<30 to ensure that entries are indexable
* using ints.
*/
static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量
/**
* The minimum capacity for per-segment tables. Must be a power
* of two, at least two to avoid immediate resizing on next use
* after lazy construction.
*/
static final int MIN_SEGMENT_TABLE_CAPACITY = 2; //每个Segement中的桶的数量
/**
* The maximum number of segments to allow; used to bound
* constructor arguments. Must be power of two less than 1 << 24.
*/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative //允许的最大的Segement的数量
/**
* Mask value for indexing into segments. The upper bits of a
* key's hash code are used to choose the segment.
*/
final int segmentMask; //掩码,用来定位segements数组的位置 /**
* Shift value for indexing within segments.
*/
final int segmentShift; //偏移量,用来确认hash值的有效位
/**
* The segments, each of which is a specialized hash table.
*/
final Segment<K,V>[] segments; //相当于多个HashMap组成的数组
static final class Segment<K,V> extends ReentrantLock implements Serializable { //内部类Segment,继承了ReentrantLock,有锁的功能
/**
* The maximum number of times to tryLock in a prescan before
* possibly blocking on acquire in preparation for a locked
* segment operation. On multiprocessors, using a bounded
* number of retries maintains cache acquired while locating
* nodes.
*/
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
/**
* The per-segment table. Elements are accessed via
* entryAt/setEntryAt providing volatile semantics.
*/
transient volatile HashEntry<K,V>[] table; //每个Segement内部都有一个table数组,相当于每个Segement都是一个HashMap
transient int count; //这些参数与HashMap中的参数功能相同
transient int modCount;
transient int threshold;
final float loadFactor;
Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
this.loadFactor = lf;
this.threshold = threshold;
this.table = tab;
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) { //向Segement中添加一个元素
}
2. 构造函数
@SuppressWarnings("unchecked")
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) //参数校验
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1; //计算segement数组的大小,并且为2的倍数,默认情况下concurrentyLevel为16,那么ssize也为16
while (ssize < concurrencyLevel) {
++sshift; //ssize每次进行左移运算,因此sshift可以看做是ssize参数左移的位数
ssize <<= 1;
}
this.segmentShift = 32 - sshift; //segement偏移量
this.segmentMask = ssize - 1; //由于ssize为2的倍数,所以sengemnt为全1的,用来定位segement数组的下标
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize; //计算每个Segement中桶的数量
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and segments[0]
Segment<K,V> s0 = //初始化第一个segement
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; //新建segements数组,并将s0赋值
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
3 . 我们来看put()方法
@SuppressWarnings("unchecked")
public V put(K key, V value) {
Segment<K,V> s;
if (value == null) //ConcurrentHashMap中value不能为空
throw new NullPointerException();
int hash = hash(key); //获取到key的hash值
int j = (hash >>> segmentShift) & segmentMask; //定位到某个segement位置
if ((s = (Segment<K,V>)UNSAFE.getObject //从上面的构造方法中我们知道,segments数组只有0位置的segment被初始化了,因此这里需要去检测计算出的位置的segment是否被初始化
由于是并发容器,所以使用UNSAFE中的方法
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false); //将元素插入到定位的Segement中
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) { //segement中的put()方法
HashEntry<K,V> node = tryLock() ? null : //获取锁,若获取不到锁,则县创建节点并返回
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table; //之后的算法就与HashMap中相似了
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock(); //释放锁
}
return oldValue;
}
4. 来具体看一下SegementMask与SegmentShift这两个变量时怎么使用的?
int sshift = 0;
int ssize = 1; //计算segement数组的大小,并且为2的倍数,默认情况下concurrentyLevel为16,那么ssize也为16
while (ssize < concurrencyLevel) {
++sshift; //ssize每次进行左移运算,因此sshift可以看做是ssize参数左移的位数
ssize <<= 1;
}
this.segmentShift = 32 - sshift; //segement偏移量
this.segmentMask = ssize - 1; //由于ssize为2的倍数,所以sengemnt为全1的,用来定位segement数组的下标
上面是构造函数中计算这两个变量的代码。
我们假设concurrencyLevel为默认值16,那么经过计算得到,ssize = 16,sshift = 4,segmentShift = 28, segementMask = 15
由于ssize为segements数组的大小,我们可以发现,当 n 与 segmentMask按位与时候正好可以得到<=15的数组,正是segements数组的下标。
int j = (hash >>> segmentShift) & segmentMask; //定位到某个segement位置
而segementShift的作用在于缩小hash值的范围,我们并不需要使用hash值所有的位,通过上面的数据,当hash值右移28位后正好可以得到有效计算的位数(4位),因此上面构造函数中的sshift也
可以表示计算segements数组时的有效位数。
ConcurrentHashMap(1.7)分析的更多相关文章
- Hashtable、ConcurrentHashMap源码分析
Hashtable.ConcurrentHashMap源码分析 为什么把这两个数据结构对比分析呢,相信大家都明白.首先二者都是线程安全的,但是二者保证线程安全的方式却是不同的.废话不多说了,从源码的角 ...
- ConcurrentHashMap源码分析(一)
本篇博客的目录: 前言 一:ConcurrentHashMap简介 二:ConcurrentHashMap的内部实现 三:总结 前言:HashMap很多人都熟悉吧,它是我们平时编程中高频率出现的一种集 ...
- ConcurrentHashMap 源码分析
ConcurrentHashMap 源码分析 1. 前言 终于到这个类了,其实在前面很过很多次这个类,因为这个类代码量比较大,并且涉及到并发的问题,还有一点就是这个代码有些真的晦涩,不好懂.前前 ...
- 死磕 java集合之ConcurrentHashMap源码分析(三)
本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...
- ConcurrentHashMap源码分析_JDK1.8版本
在jdk1.8中主要做了2方面的改进 改进一:取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数 ...
- 并发-ConcurrentHashMap源码分析
ConcurrentHashMap 参考: http://www.cnblogs.com/chengxiao/p/6842045.html https://my.oschina.net/hosee/b ...
- ConcurrentHashMap源码分析
看过hashMap源码之后一直意犹未尽的感觉,挡不住我看其他的源码了.HashMap在单线程中非常好用,也不会出现什么问题,但是一到多线程就gg了,变的不灵了.我们有HashTable可以运用在多线程 ...
- Java并发系列[9]----ConcurrentHashMap源码分析
我们知道哈希表是一种非常高效的数据结构,设计优良的哈希函数可以使其上的增删改查操作达到O(1)级别.Java为我们提供了一个现成的哈希结构,那就是HashMap类,在前面的文章中我曾经介绍过HashM ...
- ConcurrentHashMap源码分析(1.8)
0.说明 1.ConcurrentHashMap跟HashMap,HashTable的对比 2.ConcurrentHashMap原理概览 3.ConcurrentHashMap几个重要概念 4.Co ...
- java基础系列之ConcurrentHashMap源码分析(基于jdk1.8)
1.前提 在阅读这篇博客之前,希望你对HashMap已经是有所理解的,否则可以参考这篇博客: jdk1.8源码分析-hashMap:另外你对java的cas操作也是有一定了解的,因为在这个类中大量使用 ...
随机推荐
- python函数-函数对象
python函数-函数对象 实验室 当函数定义时,就类似a=1,以函数名为变量名 def zx(): pass print(zx) zx=21 print(zx) <function zx at ...
- 计算购物车金额总和( jquery )
今天简单写了一个jq版购物车计算金额总和的例子,如图: 整体页面代码如下: <!DOCTYPE html> <html> <head> <meta chars ...
- C语言I博客作业01
C语言I博客作业01 作业1 这个作业属于哪个课程? C语言程序设计I 这个作业要求在哪里? https://edu.cnblogs.com/campus/zswxy/CST2019-2/homewo ...
- 使用Python编写打字训练小程序
你眼中的程序猿 别人眼中的程序猿,是什么样子?打字如飞,各种炫酷的页面切换,一个个好似黑客般的网站破解.可现实呢? 二指禅的敲键盘,写一行代码,查半天百度-那么如何能让我们从外表上变得更像一个程序猿呢 ...
- docker的安装及常用命令
一:概述 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器或Windows 机器上,也可以实现虚拟化,容器是完全使用 ...
- 普通莫队--洛谷P1997 【faebdc的烦恼】
离散化+莫队 cnt数组表示某个颜色出现的次数 sum数组表示某个数量出现的颜色种类 其它细节问题就按照莫队的模板来的 #include<cstdio> #include<algor ...
- HDU3032 Nim or not Nim?(Lasker’s Nim游戏)
Nim or not Nim? Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)T ...
- HDU3870- intervals(差分约束)
You are given n closed, integer intervals [ai, bi] and n integers c1, ..., cn. Write a program that: ...
- ARTS-S k8s常用命令
本地访问minikube的docker eval $(minikube docker-env) 删除statefulset kubectl delete statefulset web --casca ...
- centos7下ldap+kerberos实现单点登陆
一. LDAP概念 http://wiki.jabbercn.org/index.php/OpenLDAP2.4%E7%AE%A1%E7%90%86%E5%91%98%E6%8C%87%E5%8D%9 ...