在1.7和1.8版本中,计算size()方法有写不同。先介绍1.7版本的实现。

1.7版本

在1.7版本中,有一个重要的类Segment,利用它来实现分段锁

static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
// 最大尝试获取锁次数,tryLock可能会阻塞,准备锁住segment操作获取锁。
在多处理器中,用一个有界的尝试次数,保证在定位node的时候,可以从缓存直接获取。
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
//segment内部的Hash table,访问HashEntry,通过具有volatile的entryAt/setEntryAt方法
transient volatile HashEntry<K,V>[] table;
//segment的table中HashEntry的数量,只有在lock或其他保证可见性的volatile reads中,才可以访问count
transient int count;
//在segment上所有的修改操作数。尽管可能会溢出,但它为isEmpty和size方法,
提供了有效准确稳定的检查或校验。只有在lock或其他保证可见性的volatile reads
中,才可以访问
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;
}
}
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next; HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}

刚一开始不加锁,前后计算两次所有segment里面的数量大小和,两次结果相等,表明没有新的元素加入,计算的结果是正确的。如果不相等,就对每个segment加锁,再进行计算,返回结果并释放锁。

public int size() {
final Segment<K,V>[] segments = this.segments;
int size;
boolean overflow; // true if size overflows 32 bits
long sum; // sum of modCounts
long last = 0L; // previous sum
int retries = -1; // first iteration isn't retry
try {
for (;;) {
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation
}
sum = 0L;
size = 0;
overflow = false;
for (int j = 0; j < segments.length; ++j) {
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount;
int c = seg.count;
if (c < 0 || (size += c) < 0)
overflow = true;
}
}
if (sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return overflow ? Integer.MAX_VALUE : size;
}

1.8版本

先利用sumCount()计算,然后如果值超过int的最大值,就返回int的最大值。但是有时size就会超过最大值,这时最好用mappingCount方法

public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
public long mappingCount() {
long n = sumCount();
return (n < 0L) ? 0L : n; // ignore transient negative values
}

sumCount有两个重要的属性baseCountcounterCells,如果counterCells不为空,那么总共的大小就是baseCount与遍历counterCells的value值累加获得的。

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是从哪里来的?

//当没有线程争用时,使用这个变量计数
private transient volatile long baseCount;

一个volatile变量,在addCount方法会使用它,而addCount方法在put结束后会调用

addCount(1L, binCount);
 if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x))

从上可知,在put操作结束后,会调用addCount,更新计数。

在并发情况下,如果CAS修改baseCount失败后,就会使用到CounterCell类,会创建一个对象,通常对象的volatile的value属性是1。

// 一种用于分配计数的填充单元。改编自LongAdder和Striped64。请查看他们的内部文档进行解释。
@sun.misc.Contended
static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}

并发时,利用CAS修改baseCount失败后,会利用CAS操作修改CountCell的值,

 if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}

如果上面CAS操作也失败了,在fullAddCount方法中,会继续死循环操作,知道成功。

for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
if ((as = counterCells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
CounterCell r = new CounterCell(x); // Optimistic create
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try { // Recheck under lock
CounterCell[] rs; int m, j;
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
cellsBusy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}

ConcurrentHashMap的size()方法(1.7和1.8)的更多相关文章

  1. ConcurrentHashMap的size方法是线程安全的吗?

    前言 之前在面试的过程中有被问到,ConcurrentHashMap的size方法是线程安全的吗? 这个问题,确实没有答好.这次来根据源码来了解一下,具体是怎么一个实现过程. ConcurrentHa ...

  2. ConcurrentHashmap中的size()方法简单解释

    本文所有的源码都是基于JDK1.8 ConcurrentHashmap中的size()方法源码: public int size() { long n = sumCount(); return ((n ...

  3. 并发编程 —— ConcurrentHashMap size 方法原理分析

    前言 ConcurrentHashMap 博大精深,从他的 50 多个内部类就能看出来,似乎 JDK 的并发精髓都在里面了.但他依然拥有体验良好的 API 给我们使用,程序员根本感觉不到他内部的复杂. ...

  4. Java小知识--length,length(),size()方法详细介绍

    Java中length,length(),size()区别 length属性:用于获取数组长度. eg: int ar[] = new int{1,2,3} /** * 数组用length属性取得长度 ...

  5. concurrentHashMap求size

    在 JDK1.7 中,首先会使用不加锁的模式去尝试多次计算 ConcurrentHashMap 的 size,最多三次,比较前后计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的.如果不符 ...

  6. 使用size()方法输出列表中的元素数量。需要注意的是,这个方法返回的值可能不是真实的,尤其当有线程在添加数据或者移除数据时,这个方法需要遍历整个列表来计算元素数量,而遍历过的数据可能已经改变。仅当没有任何线程修改列表时,才能保证返回的结果是准确的。

    使用size()方法输出列表中的元素数量.需要注意的是,这个方法返回的值可能不是真实的,尤其当有线程在添加数据或者移除数据时,这个方法需要遍历整个列表来计算元素数量,而遍历过的数据可能已经改变.仅当没 ...

  7. Guava中Lists.partition(List, size) 方法懒划分/懒分区

    目录 Guava中Lists.partition(List, size) 方法懒划分/懒分区 背景 分析 总结 Guava中Lists.partition(List, size) 方法懒划分/懒分区 ...

  8. concurrentHashMap的put方法详解

    本文主要介绍ConcurrentHashMap的put操作如果有错误的地方欢迎大家指出. 1.ConcurrentHashMap的put操作 ConcurrentHashMap的put操作主要有3种方 ...

  9. java length属性 length()方法 size()方法

    length是属性,一般用来说明数组的长度 length()是方法,针对字符串String说的,用来求数组中某个元素的字符串长度 String str={"adfasf",&quo ...

随机推荐

  1. Nginx进阶

    常见的web架构 LAMP - Linux +Apache +MySQL + PHP LNMP - Linux +Nginx + Mysql + PHP RHEL开源  Linux系统  RHEL C ...

  2. MCMC&Gibbs sampling

    Note of Markov Chain Monte Carlo and Gibbs Sampling :  http://pan.baidu.com/s/1jHpWY1o 序:A major lim ...

  3. HDU6183 Color it (线段树动态开点)

    题意: 一个1e6*1e6的棋盘,有两个操作:给(x,y)加上颜色c,或查找(1,y1)到(x,y2)内的颜色种类数量,最多有50种颜色 思路: 建立50颗线段树,对每个颜色的线段树,维护每个y坐标上 ...

  4. Java中类的关系

    在java里类的关系大致分为三种, 1.继承(a is b):继承extends,实现implement 2.包含(a has b):组合>聚合>关联.关系亲密度越来越小,一个类在另一个类 ...

  5. JavaWeb前置知识 : 动态和静态的区别、两种架构、常见状态码

    JavaWeb程序设计(一) : 前置知识 1.动态网页与静态网页的区别: a.不要和是否有"动感"混为一谈. b.是否随着时间.地点.用户操作的改变而改变 (例如 : 在百度上搜 ...

  6. SQL基础语法—update语句

    1 update语句介绍 update语句用来修改表中的数据内容 Single-table syntax: UPDATE [LOW_PRIORITY] [IGNORE] table_reference ...

  7. Hapi+MySql项目实战自动化文档生成(四)

    自动化生成swagger文档 使用hapi插件hapi-swagger,简单配置下插件,先修改下plugin_config.js文件: //plugin_config.js const Swagger ...

  8. Spark作业执行流程源码解析

    目录 相关概念 概述 源码解析 作业提交 划分&提交调度阶段 提交任务 执行任务 结果处理 Reference 本文梳理一下Spark作业执行的流程. Spark作业和任务调度系统是其核心,通 ...

  9. 数据算法 --hadoop/spark数据处理技巧 --(17.小文件问题 18.MapReuce的大容量缓存)

    十七.小文件问题 十八.MR的大容量缓存 在MR中使用和读取大容量缓存,(也就是说,可能包括数十亿键值对,而无法放在一个商用服务器的内存中).本次提出的算法通用,可以在任何MR范式中使用.(eg:MR ...

  10. Ansible 学习目录

    1. Ansible 安装 2. Ansible hosts 文件配置 3. Ansible 常用模块 4. Ansible playbook使用