ConcurrentHashMap源码详解

1. ConcurrentHashMap概述

ConcurrentHashMap是线程安全的哈希表,不同于HashTable,后者在方法上增加synchronized关键字,利用对象同步锁实现线程之间的同步。显然,HashTable实现线程安全的方式太“重”,并发度高的情况下,很多线程争用同一把锁,吞吐量较低。

ConcurrentHashMap通过锁分段技术,只有在同一个段内,才会存在锁竞争,提高了并发处理能力。它的内部数据结构其实是一个Segment数组,该数组的大小代表了ConcurrentHashMap的并发度,Segment同时也是一把可重入锁,该锁用来确保该段数据并发访问的线程安全。每一个Segment其实是一个类似于HashMap的哈希表,用来存储key-value。看下ConcurrentHashMap结构图:

这里写图片描述

ConcurrentHashMap维护了一个Segment数组segments,每个Segment是一个哈希表。当线程需要访问segments[1]处的哈希表,首先需要获取该段的锁,然后才能访问该段的哈希表。上图中segments数组大小为8,因此并发度为8,最多支持8个线程在不同的段同时访问。

2. HashEntry

HashEntry代表了哈希表的一个key-value项,它是ConcurrentHashMap的一个内部静态类,看下HashEntry的数据结构:

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;
    }
    //……
}

HashEntry数据结构也很简单,它是一个单链表结点,每个结点包括了key-value对、哈希值、指向下一个节点的引用。

3. Segment

ConcurrentHashMap最重要的概念就是Segment了,它是一个有锁功能的(继承了ReentrantLock)哈希表,ConcurrentHashMap正是由Segment数组组成的数据结构。

看下Segment的类声明:

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

Segment通过继承ReentrantLock拥有了锁的功能。

接着看下Segment的几个成员变量:


//获取锁失败后的尝试次数,和机器可用的cpu核数量有关
static final int MAX_SCAN_RETRIES =
  Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;

//哈希表,一个segment对应一个哈希表
transient volatile HashEntry<K,V>[] table;

//哈希表kv元素的个数,注意:ConcurrentHashMap的元素数量是所有segment的元素数量之和
transient int count;

//哈希表改变的次数
transient int modCount;

//哈希表重哈希的阀值,元素数量超过这个值,需要扩充哈希表,否则哈希冲突会增加
transient int threshold;

//加载因子
final float loadFactor;

这几个变量我们之前在学习HashMap的时候基本上都学习过,看下注释就可以了。

看下Segment唯一的一个构造方法:

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

Segment没有默认构造方法。

接着看下Segment的put方法:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    HashEntry<K,V> node = tryLock() ? null :
    scanAndLockForPut(key, hash, value);
    //到这,一定成功获取锁了。
    //注意,此时node可能为空,也可能不为空。如果为空,接下来put的时候需要创建一个新的结点,如果不为空
    //可以直接使用该节点。

    //返回key对应老的value值
    V oldValue;
    try {
      HashEntry<K,V>[] tab = table;
      //定位到HashEntry索引
      int index = (tab.length - 1) & hash;
      HashEntry<K,V> first = entryAt(tab, index);
      for (HashEntry<K,V> e = first;;) {
        if (e != null) {
          K k;
          //如果key已经存在,更新对应的value,跳出for循环
          if ((k = e.key) == key ||
              (e.hash == hash && key.equals(k))) {
            oldValue = e.value;
            if (!onlyIfAbsent) {
              e.value = value;
              ++modCount;
            }
            break;
          }
          e = e.next;
        }
        else {
          //node不为空,将node插入到链表的头部
          i
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值