java并发与多线程(二)--concurrentHashMap源码分析

本文详细分析了Java 1.8中ConcurrentHashMap的实现,从put方法入手,探讨了其扩容机制、size计算方式以及在并发环境下的线程安全性。在put操作中,涉及到了链表和红黑树的处理,当链表长度超过8时,会转换为红黑树。同时,文章还介绍了扩容过程,包括transfer方法和helpTransfer方法,以及在并发计数中如何利用CounterCell和baseCount保证准确性。
摘要由CSDN通过智能技术生成

一:简述

本文基于jdk1.8对concurrentHashMap的源码进行分析,以put()方法为入口对concurrentHashMap的扩容机制,size计算方式等代码进行分析

二:concurrentHashMap成员变量

//最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;

//默认容量大小
private static final int DEFAULT_CAPACITY = 16;

//负载因子
private static final float LOAD_FACTOR = 0.75f;

//将链表转化为红黑树的链表长度阈值
static final int TREEIFY_THRESHOLD = 8;

//将红黑树转化为链表的链表长度阈值
static final int UNTREEIFY_THRESHOLD = 6;

//将链表转化为红黑树的node数组长度阈值
static final int MIN_TREEIFY_CAPACITY = 64;

//默认的线程迁移数据范围
private static final int MIN_TRANSFER_STRIDE = 16;

//当前服务器cpu数量
static final int NCPU = Runtime.getRuntime().availableProcessors();

//真正存储数据的容器
transient volatile Node<K,V>[] table;

//用于扩容的新数组
private transient volatile Node<K,V>[] nextTable;

//和counterCells一起用于计算concurrentHashMap的size
private transient volatile long baseCount;

// -1的时候代表正在node数组正在初始化 初始化之后赋值为扩容的阈值
private transient volatile int sizeCtl;

//数据迁移的索引
private transient volatile int transferIndex;

//用于计算concurrentHashMap的size时需要加cas锁的标记
private transient volatile int cellsBusy;

//用于计算concurrentHashMap的size 默认长度是2
private transient volatile CounterCell[] counterCells;

三:concurrentHashMap的源码分析

以put方法为入口对源码进行分析

1.put方法流程图

在这里插入图片描述
2.源码分析
put()方法会调用putVal()方法,如果当前数组没有初始化那么会先调用initTable()方法初始化数组,然后根据计算好的数组下标查看当前下标下是否为null,如果是null,那么利用cas保证线程安全直接进行替换,如果不是null,那么需要解决hash冲突的问题,分链表和红黑树两种情况分别进行处理。
a.链表:遍历链表,如果有相同的key 进行覆盖的操作 否则添加到链表的尾部(尾插法)。
b.红黑树:遍历红黑树,如果有相同的key,进行覆盖操作,如果没有,那么构建红黑树的节点添加到红黑树,并且通过左旋或者右旋保证红黑树的平衡。
添加完元素之后判断链表的长度是否大于等于8,大于8 那么会调用treeifyBin()方法。
最后调用addcount()计算数组的总元素个数。

final V putVal(K key, V value, boolean onlyIfAbsent) {
   
        //键和值都不能为空 否则抛出空指针异常
        if (key == null || value == null) throw new NullPointerException();
	//计算key的hash值
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
   
            Node<K,V> f; int n, i, fh; K fk; V fv;
		//如果tab 为空 那么需要先初始化数组
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
		//(n - 1) & hash 计算当前的值应该存放的数组下标	
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
   
		//如果计算出的数组位置的node为null 直接用cas进行替换即可
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                    break;                   // no lock when adding to empty bin
            }
		//MOVED 代表当前节点正在进行数据迁移 那么直接去协助扩容
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
		//onlyIfAbsent true表示不能覆盖原有的值 默认是false
            else if (onlyIfAbsent // check first node without acquiring lock
                     && fh == hash
                     && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                     && (fv = f.val) != null)
               //如果存在相同的key 并且value不为空 直接返回已存在的值	 
                return fv;
            else {
   
		//这段代码是处理存在hash冲突的情况的逻辑
                V oldVal = null;
		//针对node进行加锁
                synchronized (f) {
   
                    if (tabAt(tab, i) == f) {
   
					    //针对链表的处理
                        if (fh >= 0) {
   
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
   
                                K ek;
				//遍历链表 如果存在有相同的key 而且允许被覆盖 那么直接覆盖原有的值
				//不需要cas来保证线程安全 因为已经加了锁
                                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);
                                    break;
                                }
                            }
                        }
						//针对红黑树的处理
                        else if (f instanceof TreeBin) {
   
                            Node<K,V> p;
                            binCount = 2;
			//
			//也就是存在key相同的并且允许被覆盖就覆盖旧的值 不然就根据红黑树的规则添加到树中
			//putTreeVal() 解决hash冲突的逻辑和链表一样 但是会涉及到树的左旋和右旋
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
   
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }
                if (binCount != 0) {
   
		//查看链表长度是否达到了树化的阈值(默认是8)
		// 注意 这里并不是说达到了阈值就会树化 
		//而是要满足数组长度大于64而且链表长度大于等于阈值两个条件才会树化 否则会先进行扩容来减少链表的长度
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值