ConcurrentHashMap 学习笔记

本文详细介绍了Java ConcurrentHashMap的实现原理,包括其线程安全的实现方式、关键属性如sizeCtl,以及put()、initTable()、addCount()、transfer()等核心方法的详细逻辑。文章还探讨了协助扩容的helpTransfer()方法以及在get()操作中的处理策略。通过阅读,读者可以深入理解ConcurrentHashMap的工作机制。
摘要由CSDN通过智能技术生成

# ConcurrentHashMap

HashMap虽然好用,但是它却不是线程安全的,而在并发度较高的现在,在有些情况下它可能就不是那么合适了,所以需要一个线程安全键值对结构。

Hashtable是线程安全的,但是它却过于笨重了,相比于HashMap而言,它仿佛只是在HashMap的每一个方法上加了一个synchronized,效率可想而知。所以就有了我们今天这篇问文章的主角ConcurrentHashMap,毕竟讲HashMap不讲ConcurrentHashMap那就是耍流氓,想要了解HashMap的同学可以去[HashMap源码及其常见问题详解](https://blog.csdn.net/weixin_42737868/article/details/113256318)看看。

注意,我们这里讲解的ConcurrentHashMap同样是Java8的。

## 关键属性

ConcurrentHashMap为什么能保证线程安全的呢,简单来说是**通过CAS+synchronized来实现的**,而且他最大的特点是能够多个线程协助扩容,至于是怎么实现的且听我慢慢道来。

```java
private transient volatile int sizeCtl;
```

ConcurrentHashMap相比于HashMap多了一个关键的属性`sizeCtl`,它有这几种情况:

1. -1:代表正在初始化;
2. -N:代表有N-1个线程正在协助扩容;
3. 0或N:当table为null的时候代表初始容量,0代表默认容量,否则代表扩容阈值;

可以看出`sizeCtl`身兼数职,它不但代替了HashMap的`threshold`属性,还在数组初始化和扩容的时候有着至关重要的作用。我们先对他有个大致的了解,具体的应用我们下文再说。

static final class ForwardingNode<K,V> extends Node<K,V> {
    final Node<K,V>[] nextTable;
    ForwardingNode(Node<K,V>[] tab) {
        super(MOVED, null, null, null);
        this.nextTable = tab;
    }
}

另一个巨大的改变是多了一个特殊的节点`ForwardingNode`,他只有在扩容的时候用的到,ConcurrentHashMap**在扩容的时候将这个节点插入桶的头部,来告诉其他线程这个桶正在被迁移。**`nextTable`是扩容的时候的**新数组**。`ForwardingNode`节点的**key和value都是null**,所以ConcurrentHashMap**不允许节点的key或value为空**。`ForwardingNode`节点的hash值默认是MOVEN,也就是-1,同样我们可以看到红黑树根节点的hash值是-2。

static final int MOVED     = -1; // hash for forwarding nodes
static final int TREEBIN   = -2; // hash for roots of trees

他跟HashMap的另一个属性的不同是多了一个`nextTable`,他只在扩容的时候有用,其他时候都是null。


/**
 * The next table to use; non-null only while resizing.
 */
private transient volatile Node<K,V>[] nextTable;


 

## put()

ConcurrentHashMap的构造函数跟HashMap大差不差,都是对一些关键属性的赋值,如果没有指定就全都是默认,所以我们这里就不细讲了,有兴趣的可以看我上一篇文章,当然我也推荐你看完上一篇文章之后再来学习这一篇文章。

`put()`可以说是HashMap最核心的方法了,把这个方法完全搞懂基本上就把HashMap全部学会了,所以我们这次依旧来深入`put()`方法。

//key value都不能为null
public V put(K key, V value) {
    return putVal(key, value, false);
}


 

`put()`还是一样,实际逻辑都在`putVal()`里。


final V putVal(K key, V value, boolean onlyIfAbsent) {
    //不允许key或value为空
    if (key == null || value == null) throw new NullPointerException();
    //算出hash值
    int hash = spread(key.hashCode());
    //用来记录桶中从头结点到该节点的元素个数
    int binCount = 0;
    //死循环插入节点
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        //还未初始化的时候先进行初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //如果找到的这个桶中没有元素直接CAS添加进去
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // 添加的时候没有加锁
        }
        //hsah值是-1的是ForwardingNode节点,说明正在扩容
        else if ((fh = f.hash) == MOVED)  //static final int MOVED = -1; 
            //协助扩容
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            //存在链表且没在扩容,就将头结点锁住
            synchronized (f) {
                //再次效验节点有没有发生改变
                if (tabAt(tab, i) == f) {
                    //hash值>=0代表是链表
                    if (fh >= 0) {
                        binCount = 1;
                        //遍历链表
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            //存在这个key就覆盖(如果可以覆盖的话)
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            //不存在就新增一个节点(尾插法)
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    //对红黑树的处理
                    else if (f instanceof TreeBin) { 
                        Node<K,V> p;
                        binCount = 2;
               
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值