在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. ros之tf坐标系广播与监听的编程实现

    创建功能包-learning_tf $ cd ~/catkin_ws/src $ catkin_create_pkg learning_tf roscpp rospy tf turtlesim 如何创 ...

  2. 为什么Netflix没有运维岗位?

    Netflix 是业界微服务架构的最佳实践者,其基于公有云上的微服务架构设计.持续交付.监控.稳定性保障,都为业界提供了大量可遵从的原则和实践经验. 在运维这个细分领域,Netflix 仍然是最佳实践 ...

  3. SDL初始化和创建窗口

    //初始化SDL2和创建一个窗口,并且将屏幕绘制成大红色 #include <iostream> extern "C" { #include <SDL.h> ...

  4. 【Qt学习笔记】Qt+VS2010的配置

    http://blog.csdn.net/jocyln9026/article/details/8575218 关于Qt Qt是1991年由Trolltech公司开发的一个跨平台的C++图形用户界面应 ...

  5. 尝试用 Python 写了个病毒传播模拟程序

    病毒扩散仿真程序,用 python 也可以. 概述 事情是这样的,B 站 UP 主 @ele 实验室,写了一个简单的疫情传播仿真程序,告诉大家在家待着的重要性,视频相信大家都看过了,并且 UP 主也放 ...

  6. 为什么尽量不要使用Executors创建线程池

    看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,通过源码分析禁用的原因. 线程池的优点 管理一组工作线程,通过线程池 ...

  7. To be contine ,NW NMM backup sqlserver failed.

    Last time, we talk about separate under one cluster backup into two diffetent AG backup. Does it wor ...

  8. Go语言实现:【剑指offer】孩子们的游戏

    该题目来源于牛客网<剑指offer>专题. 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此.HF作为牛客的资深元老,自然也准备了一些小游戏.其中,有个游戏是这样的 ...

  9. My introduction

    Vistors 访客统计

  10. Csp_J2019游记

    Day_-14 学校开始停课集训,还好还有上午~~ Day_-7 马上半期考试+\(Csp\),心态已炸,却还要坚持集训 Day_-1 \(Csp\)前夕,打算临时抱抱佛脚,死磕了一下图论(诸如最小生 ...