ConcurrentHashMap1.7源码解读

本文详细介绍了ConcurrentHashMap1.7的实现原理,包括其继承关系、底层数据结构、构造方法、常用操作如put、get、remove、size等的实现细节,以及涉及到的Unsafe类和并发控制策略。文章还探讨了为什么ConcurrentHashMap不允许null值的原因。
摘要由CSDN通过智能技术生成

写在前面的话

有所坚持才会有所得,相信行动的力量。

继承关系

继承AbstractMap,实现ConcurrentMap和Serializable接口。

具备map的基本属性,可序列化。

ConcurrentMap接口,是一个能够支持并发访问的java.util.map集合,

在map的基础上提供了4个接口

	//插入元素
    V putIfAbsent(K key, V value);
 
    //移除元素
    boolean remove(Object key, Object value);
 
    //替换元素
    boolean replace(K key, V oldValue, V newValue);
 
    //替换元素
    V replace(K key, V value);

底层结构

ConcurrentHashMap在初始化时会要求初始化concurrencyLevel作为segment数组长度,即并发度,

代表最多有多少个线程可以同时操作ConcurrentHashMap,默认是16,

每个segment片段里面含有键值对HashEntry数组,

是真正存放键值对的地方。这就是ConcurrentHashMap的数据结构。

数组+链表

具体说来:

分段锁数组+HashEntry数组+链表组成

HashEntry–> Segment–> ConcurrentHashMap

基本属性

     //默认的初始容量
    static final int DEFAULT_INITIAL_CAPACITY = 16;

	//默认加载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

	//默认的并发度,也就是默认的Segment数组长度
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;

    //最大容量,ConcurrentMap最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    
	//每个segment中table数组的长度,必须是2^n,最小为2
    static final int MIN_SEGMENT_TABLE_CAPACITY = 2;

	//允许最大segment数量,用于限定concurrencyLevel的边界,必须是2^n
    static final int MAX_SEGMENTS = 1 << 16; // slightly conservative

	//非锁定情况下调用size和contains方法的重试次数,避免由于table连续被修改导致无限重试
    static final int RETRIES_BEFORE_LOCK = 2;

	//计算segment位置的掩码值
    final int segmentMask;

	//用于计算算segment位置时,hash参与运算的位数
    final int segmentShift;
    //segmentMask 和 segmentShift作用主要是根据key的hash值做计算定位在哪个Segment片段。

	//Segment数组
    final Segment<K,V>[] segments;

构造方法

传入初始容量,负载因子和并发读的构造方法

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
		//找到一个大于等于传入的concurrencyLevel的2^n数,且与concurrencyLevel最接近
		//ssize作为Segment数组,必须使2的幂
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
         // 默认值,concurrencyLevel 为 16,sshift 为 4
    	// 那么计算出 segmentShift 为 28,segmentMask 为 15,后面会用到这两个值
        this.segmentShift = 32 - sshift;
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
		// 计算每个segment中table的容量
        // 这里根据 initialCapacity 计算 Segment 数组中每个位置可以分到的大小
    	// 如 initialCapacity 为 64,那么每个 Segment 或称之为"槽"可以分到 4 个
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        // 默认 MIN_SEGMENT_TABLE_CAPACITY 是 2,这个值也是有讲究的,因为这样的话,对于具体的槽上,
    	// 插入一个元素不至于扩容,插入第二个的时候才会扩容
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
		// 确保cap是2^n
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
		// 创建segments并初始化第一个segment数组,其余的segment延迟初始化
        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];
        // 往数组写入 segment[0]
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

可以看出Segment的数组大小必须使2的幂,最小为2,里面的tableEntity数组也必须使2的幂,最小也为2.

只初始化了segment数组0上的值,其他位置仍然是 null。

无参构造方法

public ConcurrentHashMap(){}

默认容量为16,负载因子为0.75,并发度为16.

Segment介绍

分段锁

继承于重入锁ReentrantLock,要想访问Segment片段,线程必须获得同步锁

 static final class Segment<K,V> extends ReentrantLock implements Serializable {

	//尝试获取锁的最多尝试次数,即自旋次数
    static final int MAX_SCAN_RETRIES =
            Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;

	//HashEntry数组,也就是键值对数组,volatile修饰,线程可见性
    transient volatile HashEntry<K, V>[] table;
	//元素的个数
    transient int count;
	//segment中发生改变元素的操作的次数,如put/remove
    transient int modCount;
	//当table大小超过阈值时,对table进行扩容,值为capacity *loadFactor
    transient int threshold;
	//加载因子
    final float loadFactor;

    Segment(float lf, int threshold, HashEntry<K, V>[] tab) {
        this.loadFactor = lf;
        this.threshold = threshold;
        this.table = tab;
    }
}

HashEntity介绍

源码

static final class HashEntry<K,V> {
	//hash值
    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;
    }
}

键值对HashEntry是ConcurrentHashMap的基本数据结构,多个HashEntry可以形成链表用于解决hash冲突。

和hashmap的entry是一样的结构。

常用方法

put

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值