并发()——ConcurrentHashMap 源码解析

本文详细介绍了ConcurrentHashMap在JDK1.7和1.8中的实现原理。JDK1.7采用分段锁,由Segment数组和HashEntry组成,而JDK1.8中摒弃了Segment,采用CAS和synchronized实现并发安全,增加了红黑树的数据结构提高查询效率。文章深入探讨了不同版本中的初始化、并发度、扩容策略、put方法以及get方法的实现,并对比了两者之间的差异。
摘要由CSDN通过智能技术生成

一、 HashMapHashTableConcurrentHashMap的区别

  • HashMap是线程不安全的。

  • HashTable是线程安全的。HashTable的线程安全是采用在每个方法上面都添加上了 synchronized 关键字来修饰,即 Hashtable 是针对整个 table 的锁定。所有访问 HashTable 的线程都必须竞争同一把锁。当一个线程访问 HashTable 的同步方法时,其他线程再访问 HashTable的同步方法时,可能会进入阻塞或者轮询的状态。比如:线程 1 使用put 方法来添加元素,线程2不但不能使用put 方法添加元素,也不能使用 get方法来获取元素,所以锁竞争越激烈,该类的效率越低。

  • ConcurrentHashMap使用分段锁技术, ConcurrentHashMap 是由多个 Segment组成(Segment下包含多个 Node,即键值对),每个Segment都有把锁来实现线程安全,当一个线程占用锁访问其中一段数据的时候,其他段的数据也能被其他线程访问。

二、ConcurrentHashMap 在 JDK1.7 中

2.1 概述

jdk1.7中采用了 segment + HashEntry 的方式进行实现。

在这里插入图片描述
在这里插入图片描述

  • 一个ConcurrentHashMap 中包含一个 Segment<K,V>[] segments 数组
  • 一个Segment对象中包含一个 HashEntry<K,V>[] table 数组
  • 一个HashEntry对象包含

如图所示,1.7的ConcurrentHashMap 是由 Segment + HashEnttry 组成,和HashMap一样,仍然是数组+链表。

ConcurrencyLevel:默认是16,也就是说ConcurrentHashMap有16个Segments,所以理论上,这个时候,最多可以同时支持16个线程并发写,只要它们的操作分别分布在不同的Segment上。这个值在初始化的时候可以设置为其他值,但是一旦初始化后,它是不可以扩容的。

2.2 成员变量

ConcurrentHashMap 在初始化的时候,计算出 Segment数组的大小 ssize 和每个 Segment 中 HashEntry 数组的大小 cap,并初始化Segment 数组的第一个元素;

  • ssize:大小为2的幂次方,默认为16
  • cap:大小也为2的幂次方,最小值为2,最终结果根据初始化容量 initialCapacity 进行计算。

它的核心成员变量

在这里插入图片描述

Segment 是 ConcurrentHashMap 的一个内部类,主要的组成结构如下:

在这里插入图片描述

首先通过 key 定位到 Segment,之后在对应的Segment中进行具体的put操作。

其中的HashEntry组成:

在这里插入图片描述

和HashMap非常相似,唯一的区别就是其中的核心数据如value,以及链表都是volatile修饰的,保证了获取时的可见性。

2.3 并发度(Concurrency Level)

并发度可以理解为程序运行时能够同时更新ConcurrentHashMap且不产生锁竞争的最大线程数,实际上就是ConcurrentHashMap中的分段锁的个数,即 Segment[] 的数组长度。ConcurrentHashMap 默认的并发度是16,但用户也可以在构造函数中设置并发度。当用户设置并发度的时候,ConcurrentHashMap 会使用大于等于该值的最小2幂指数作为实际并发度(如果用户设置并发度为17,实际并发度则为32)。

运行时,通过将 key 的高 n 位n = 32- segmentShift和并发度-1 做位与运算定位到所在的Segment

2.4 初始化
  • initialCapacity:初始容量,这个值指的是整个 ConcurrentHashMap 的初始容量,实际操作的时候需要平均分给每个Segment
  • loadFactor:负载因子,Segment数组不可以扩容,所以这个负载因子是给每个Segment内部使用的。

注意: JDK7中除了第一个 Segment之外,剩余的Segments采用的是延迟初始化的机制; 每次put之前需要检查key对应的Segment 是否为 null,如果是则调用 ensureSegment() 以确保对应的 Segment被创建。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ConcurrentHashMap 是 Java 中的一个线程安全的哈希表,它是通过分段锁技术实现线程安全。下面对 ConcurrentHashMap源码进行简要解析。 ### 数据结构 ConcurrentHashMap 内部维护了一个 Segment 数组,每个 Segment 都是一个独立的哈希表,而且这些哈希表的数量可以在创建 ConcurrentHashMap 时指定。每个 Segment 内部都是一个类似 HashMap 的数据结构,也就是一个数组加链表的结构。ConcurrentHashMap 中的所有操作都是先定位到对应的 Segment,然后在 Segment 中进行操作。 ### put 方法 ConcurrentHashMap 的 put 方法首先会调用 hash 方法计算键的哈希值,然后根据哈希值找到对应的 Segment。接着会调用 Segment 的 put 方法,这个方法会加锁并且调用内部的 put 方法将键值对放入内部的 HashMap 中。如果 put 时,HashMap 中已经存在了这个键值对,那么就会更新这个键值对的值。最后释放锁。 ### get 方法 ConcurrentHashMap 的 get 方法也是先定位到对应的 Segment,然后调用内部的 get 方法,在内部的 HashMap 中查找键对应的值。由于在查找的过程中没有加锁,所以在多线程的情况下可能会出现一些数据不一致的问题,但是这个问题被认为是可以接受的,因为它不会影响数据的正确性。 ### size 方法 ConcurrentHashMap 的 size 方法也是先定位到对应的 Segment,然后调用内部的 count 方法,这个方法返回的是当前 Segment 中键值对的数量。最后将所有 Segment 中的键值对数量相加得到 ConcurrentHashMap 的大小。 ### 总结 ConcurrentHashMap 是通过分段锁技术实现线程安全的哈希表,它的内部维护了一个 Segment 数组,每个 Segment 都是一个独立的哈希表。ConcurrentHashMap 中的所有操作都是先定位到对应的 Segment,然后在 Segment 中进行操作。在 put 操作的过程中会加锁,而在 get 操作的过程中不会加锁,所以在多线程的情况下可能会出现一些数据不一致的问题,但是这个问题被认为是可以接受的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值