目 录CONTENT

文章目录
Map

ConcurrentHashMap

FatFish1
2025-06-13 / 0 评论 / 0 点赞 / 2 阅读 / 0 字 / 正在检测是否收录...

简介

是一种保证线程安全的mapHashMap是非线程安全的,而HashTable和ConcurrentHashmap都是线程安全的。而且ConcurrentMap比HashTable性能好得多。

对比HashMap:

HashMap的key和value均可以为null,而HashTable和Concur rentHashMap的key和value均不可以为null

对比HashTable:保证线程安全的方式不同

  • HashTable是通过给整张散列表加锁的方式来保证线程安全,这种方式保证了线程安全,但是并发执行效率低下。

  • JDK1.8开始, ConcurrentHashMap数据结构与1.8中的HashMap保持一致,均为数组+链表+红黑树,是通过乐观锁+Synchronized来保证线程安全的。

源码

核心成员变量包括:

// Node数组,存放map元素
transient volatile Node<K,V>[] table;

Node - ConcurrentHashMap的内部数据结构

继承自HashMap的Entry,核心成员变量:

final K key;
volatile V val;
volatile Node<K,V> next;

根据next可以看出来,是一个链表,且用lvolatile修饰value和next,是放置扩容时发生变化,使用volatile保持可见性和避免重排序

setValue

public final V setValue(V value) {
    throw new UnsupportedOperationException();
}

关键在于setValue,不允许修改值

TreeNode

当链表的节点数大于8时会转换成红黑树的结构,他就是通过TreeNode作为存储结构代替Node来转换成黑红树

核心成员变量:

TreeNode<K,V> parent;  // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;    // needed to unlink next upon deletion

可以看出来红黑树的数据结构是一个标准的二叉树

TreeBin

TreeBin是TreeNode的容器,提供红黑树转换和一些锁控制,核心成员变量包括:

static final int WRITER = 1; // 状态:持有写锁
static final int WAITER = 2; // 状态:等待写锁
static final int READER = 4; // 状态:持有读锁累计数量增加

ConcurrentHashMap操作

put操作的核心思想:

  • 如果数组tab不存在,使用unsafe提供的同步能力初始化数组

  • 如果对应哈希位没有元素,使用CAS操作插入

  • 如果有哈希冲突,使用sychronized锁表

putVal

int hash = spread(key.hashCode()); //两次hash,减少hash冲突,可以均匀分布

首先两次hash,减少哈希冲突,然后遍历table数组

Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
    tab = initTable();

这里做了一个懒汉模式的初始化

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
     if (casTabAt(tab, i, null,

第一个场景:判定数组当前哈希位置上没有Node,直接通过CAS做Node插入

else if ((fh = f.hash) == MOVED)
    tab = helpTransfer(tab, f);

第二个场景:如果map正在扩容,需要帮助扩容

else {
    V oldVal = null;
    synchronized (f) {

最终场景:存在hash冲突,这是就要用synchronized加锁了,下面的流程和map相似,即判定该位置的是链表还是红黑树,分别走不同的插入流程

addCount(1L, binCount);

插入完后有一个数量增加的方法,判定是否需要扩容

可以看出来,使用ConcurrentHaspMap的put方法锁是比较重的直接就sychronized排他了,这时候其他线程也没法get

get

也是两次哈希,然后判断条件包括:

  • table不为空

  • table对应哈希位置的链表不为空

  • 头节点不为空

符合判断条件,找到对应位置的val值

else if (eh < 0)
    return (p = e.find(h, key)) != null ? p.val : null;

这个场景表明table[i]为一棵树或正在迁移,通过find方法寻找对应值,这里的负值是在fullAddCount方法里面更新的,这里包含两个场景:

  • eh=-1,说明正在扩容,该节点是ForwardingNode(树头节点),调用的是ForwardingNode的find方法,去nextTable找

  • eh=-2,说明是一个TreeBin,调用TreeBin的find方法,而红黑树有可能存在变色旋转,因此树的find方法里面是有读写锁的

可以看出来但是get方法正常情况下是全程无锁的,因此ConcurrentHashMap的效率高,而其不需要加锁的原因是:

  • get访问的变量是volatile关键字修饰的,如Node.val、Node.next、count,volatile关键字保证了其值修改对其他线程可见

  • 引用类型的table数组也有volatile修饰,保证了数组扩容时引用改变对其他线程可见

  • 当节点是红黑树的时候,如果树正在变色旋转并且要查询的值不是树头节点,会加一个读写锁

0

评论区