【ConcurrentHashMap】JDK1.7版本源码解读与分析

如果对文章中提到的与 HashMap 相关的部分有任何疑问, 请移步HashMap源码详解

简介

在这里插入图片描述

  • 底层是一个 Segment[] 数组, 每个 Segment对象 内部又有一个 Entry[ ] 数组, 一个 Entry[] 数组就相当于一个HashMap

  • Entry[ ]采用拉链法解决冲突, 但是没有红黑树, 红黑树是1.8才引入的;

  • 一个 Segment对象就是一个段; 当往容器中添加元素调用 put 方法时, 锁的粒度就是一个段;

  • 调用 put 方法时, 先计算应该放到 Segment[ ] 中的哪个段, 然后调用 segment.put() 方法将 Entry 插入到 segment.Entry[ ],可以看出, 一次插入有两次散列,一次负责选择在 Segment[] 中的位置, 一次负责选择 Segment 内 Entry[] 的位置;

  • 两次散列使用的 hash 值不同;

  • Segment数组的长度是固定的, 构造以后就不会再扩容, 内部的Entry[ ] 是一个个独立的Map, 会各自扩容, 互相之间的大小没有必然关系;

  • ConcurrentHashMap拉链时采用的是头插法;

构造方法

  • 默认的 initialCapacity = 16, loadFactor = 0.75, concurrentcyLevel = 16;

  • concurrencyLevel 经一些计算, 得到 Segment数组 的长度, 并且不再改变.

    具体逻辑为: 取 >= concurrencyLevel 的, 最小的, 2的整数次幂作为 Segment 数组的长度; 原理是将 1 不断左移, 直到 >= concurrencyLevel参数;

  • initialCapacity 和 concurrencyLevel 经计算得到 segment[0] 中Entry数组的长度;

    具体逻辑为: (initialCapacity / concurrentcyLevel) 向上取整得tmp, 如果tmp <= 2, 直接取 capacity = 2; 如果tmp > 2, 再取 >= temp 的, 最小的, 2的整数次幂;

  • 在默认情况下, 最终 Segment 数组长度为 16, 一个 Segment 内部的 Entry 数组长度为 2;

  • 根据计算出的尺寸, 创建一个 Segment 对象 s0;

  • 使用 UNSAFE.putOrderedObject(ss, SBASE, s0) 系统调用将 s0 直接放到 segment数组 下标为0的位置;

  • 经过构造后, 只有segment[0] 的位置有 非null segment对象, 其余位置将在各自首次添加元素的时候复制 segment[0] 的参数, 进行初始化; s0 就是一个原型;

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; // 用于记录segment数组的长度
        while (ssize < concurrencyLevel) {
   
            ++sshift;
            // 通过不断左移的方式得到 >= concurrencyLevel 的2的整数次幂, 时间复杂度为32
            ssize <<= 1;
        }
        this.segmentShift = 32 - sshift; // 散列到segements和enntries时要使用不同的hash值, 通过hash值移位实现
    	// 用于取&, 代替取模操作, 因为segment数组长度不会再变化, 所以这里记录下来, 以后直接用
        this.segmentMask = ssize - 1; 
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize; // 用于记录Entry数组长度
        if (c * ssize < initialCapacity)
            ++c; // 相当于向上取整 
        int cap = MIN_SEGMENT_TABLE_CAPACITY; // 保证最小为2
        while (cap < c)
            cap <<= 1; // 取2的整数次幂
    
    	// new 出一个segment对象, 作为 ss[0]
        Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                             (HashEntry<K,V>[])new
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值