java并发容器ConcurrentHashMap源码分析,java分布式面试题及答案

2.1 ConcurrentHashMap构造方法与数据结构分析


/**

  • Creates a new, empty map with the specified initial

  • capacity, load factor and concurrency level.

  • @param initialCapacity the initial capacity. The implementation

  • performs internal sizing to accommodate this many elements.

  • @param loadFactor the load factor threshold, used to control resizing.

  • Resizing may be performed when the average number of elements per

  • bin exceeds this threshold.

  • @param concurrencyLevel the estimated number of concurrently

  • updating threads. The implementation performs internal sizing

  • to try to accommodate this many threads.

  • @throws IllegalArgumentException if the initial capacity is

  • negative or the load factor or concurrencyLevel are

  • nonpositive.

*/

@SuppressWarnings(“unchecked”)

public ConcurrentHashMap(int initialCapacity,

float loadFactor, int concurrencyLevel) { //@1

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; //@2 start

int ssize = 1;

while (ssize < concurrencyLevel) {

++sshift;

ssize <<= 1;

}

this.segmentShift = 32 - sshift;

this.segmentMask = ssize - 1; //@2 end

if (initialCapacity > MAXIMUM_CAPACITY)

initialCapacity = MAXIMUM_CAPACITY;

int c = initialCapacity / ssize;

if (c * ssize < initialCapacity)

++c;

int cap = MIN_SEGMENT_TABLE_CAPACITY; // 每个 segment 内部容里

while (cap < c)

cap <<= 1;

// create segments and segments[0]

Segment<K,V> s0 =

new Segment<K,V>(loadFactor, (int)(cap * loadFactor),

(HashEntry<K,V>[])new HashEntry[cap]);

Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];

UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]

this.segments = ss;

}

代码@1 concurrencyLevel,含义,并发级别,并发度,在键不冲突的情况下,最多允许多少个线程同时访问数据不需要阻塞(理想情况下),我们应该知道,ConcurrentHashMap 的基本实现原理就是引入Segment 数据结构,将锁的粒度细化到Segment, 也就是说,如果多个线程,同时操作多个 key,如果这些 key,分布在不同的 Segment, 那这些线程的操作互不影响,当然不需要加锁,提高性能。所以 concurrencyLevel,就是要求告诉 ConcurrentHashMap, 我需要这么过个线程同时访问你而不产生锁冲突。

代码@2,ssize,该变量的值等于ConcurrentHashMap 中 segment 的长度,也就是 Segment[]  的长度。该值取决于concurrencyLevel, 其实就是小于concurrencyLevel 的最大的2的幂,比如concurrencyLevel= 16,那 ssize=16,如果 concurrencyLevel = 12, ssize=8,因为ssize的长度为2的幂。

变量shift的值,看出来了没,其实就是 ssize 2 ^ shift,其实就是表示ssize需要的二进制位。

segmentMask、segmentShift ,这两个属性在该表达式中使用:(h >>> segmentShift) & segmentMask),很明显,就是用来算Segment[]数组中的下标来的。意图segmentShift = 32 - sshift,也就是利用hash的高位与代表(ssize-1)来定位下标。// 如果默认,初始容量16,那么ssize=16, sshift=4 定位端 hash 无符号向右移多少28位,(总共32位),那就是使原本32-29位参与运算(高位)

变量cap,就是每个Segment中HashEntity[]的长度,大于【初始容量/segment长度】的最小2的幂。

分析到这里,ConcurrentHashMap就构建成功了,我们先重点关注一下 Segment 的数据结构。

Segment 段的内部数据结构如下:

  • 类的声明:static final class Segment<K,V> extends ReentrantLock implements Serializable

  • 数据结构:

transient volatile HashEntry<K,V>[] table;    // 内部键值对

transient int count;  // 元素数量

transient int modCount;     // 结构发生变化的次数

transient int threshold;    // 扩容时的阔值

final float loadFactor;     // 扩容因子,主要影响threshold,影响什么时候扩容

对上述结构,是否似曾相识,对了,就是它,HashMap;每个 Segment 其实就是一个 HashMap; 还有一个很关键点:Segment继承自 ReentrantLock, 也就是 Segment 本身就是一把锁。

2.2  public V put(K key, V value) 源码分析


public V put(K key, V value) {

Segment<K,V> s;

if (value == null)

throw new NullPointerException(); // @1

int hash = hash(key);

int j = (hash >>> segmentShift) & segmentMask; //@2

if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck in ensureSegment

(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment @3

s = ensureSegment(j); //@4

return s.put(key, hash, value, false); //@5

}

代码@1,表明 ConcurrentHashMap不支持value为空的键值对。

代码@2,计算该key对应的Segment的位置(数组下标),并发包中获取数组元素的方式,采用的是UNSAFE直接操作内存的方式,而不是典型的  Segment[] a = new Segment[16],  第j个元素的值为  a[j]。如果需要详细了解UNSAFE操作数组元素的原理,请查看  另一篇博客(AtomicIntegerArray 源码分析)

比如一个Integer[]中,每个int是32位,占4个字节,那数组中第3个位置的开始字节是多少呢?=(3-1) << 2,也就是说SHIFT的值为元素中长度的幂。怎么获取每个元素在数组中长度(字节为单位)= UNSAFE.arrayIndexScale,

而 UNSAFE.arrayBaseOffset,返回的是,第一个数据元素相对于对象起始地址的便宜量,该部分的详解,请参考我的技术博客【http://blog.csdn.net/prestigeding/article/details/52980801

代码@3,就是获取j下标的segment对象。相当于   if(   (s == segments[j])== null  )

代码@4,我们将目光移到 ensureSegment方法中:

/**

    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值