本文主要介绍ConcurrentHashMap的put操作如果有错误的地方欢迎大家指出。

1、ConcurrentHashMap的put操作

ConcurrentHashMap的put操作主要有3种方式

/**
 *
 * @param key 传入的key
 * @param value value传入的value
 * @return 如果写入冲突(说明此前有和他相同的节点,也就是key和hash值和他一模一样),则返回冲突的节点的值,如果没有冲突,则返回null
 */
public V put(K key, V value) {
return putVal(key, value, false);
}
/**
 *  将原有的Map中的键值对全部移动到现在的HashMap中,
 *  如果节点已经存在(key相同并且hashcode相同),则把原来的值进行更新反之添加到集合中
 */
public void putAll(Map<? extends K, ? extends V> m) {
    tryPresize(m.size()); //重新调整表的大小使得能够容纳所有的元素
    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
        putVal(e.getKey(), e.getValue(), false);
}
/**
 * 和put方法类似,只是如果发生写冲突,不进行旧值的更新
 */
public V putIfAbsent(K key, V value) {
return putVal(key, value, true);
}

本文主要分析第一种方式(其他方式基本一样),废话不多说,直接上源码

//这个方法为put方法的入口方法,他调用了putVal方法,没什么好说的
public V put(K key, V value) {
return putVal(key, value, false);
}
/**
 *
 * @param key 传入的key
 * @param value 传入的value
 * @param onlyIfAbsent 如果为true,表示如果发生写冲突,旧值不进行更新,如果为false,则旧值进行更新
 * @return 如果有写冲突则返回原来的旧的value,没有则返回null
 */
final V putVal(K key, V value, boolean onlyIfAbsent) {
   //如果传入的key或者value为null,则抛出空指针异常(注意,这里有HashMap有比较大的区别)
if (key == null || value == null) throw new NullPointerException();
    //通过hash算法计算出key对应的hash值
    int hash = spread(key.hashCode());
     //binCount = 0 : 表示对应的桶位还没有放入过值,直接放入
    //binCount = 2 : 表示有可能是树形结构
   //binCount为其他数字,有具体含义,下面会介绍
    int binCount = 0;

    //通过自旋方式放入值
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        //表示是第一次放入值,表还没有进行初始化,则先进行初始化工作(初始化工作不是在构造函数中完成的,而是在第一次put的时候才进行)
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //cas获得所在桶位的头节点
     //如果桶位的头结点为null,则可以尝试使用cas给头节点赋值(这里的put没有进行加锁)
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
       //如果cas设置成功,则跳出当前的自旋,如果失败,说明当前有竞争,进入下一次自旋
if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
break;                   
        }
//如果当前节点的hash值为MOVED,表示当前节点为forwarding节点,表示散列表正在扩容,则尝试帮助其他线程扩容
        else if ((fh = f.hash) == MOVED)
        //尝试帮助扩容的方法,下面会详解
            tab = helpTransfer(tab, f);
        //到这说明当前节点为链表或者红黑树,则加锁put
        else {
//记录下oldVal的值,如果有冲突,会把冲突的值写入到这个变量中,最后会return这个值
            V oldVal = null;
            //put操作对头节点加锁
            synchronized (f) {
//判断当前的头节点是不是之前的头节点,有可能在这么多判断过程中,头节点已经被改动过
                if (tabAt(tab, i) == f) {
//fh>=0 表示当前为链表节点
                    if (fh >= 0) {
//1 当前插入key与所有key都不一样时,即没有发生写冲突,把node插入到链表末尾,并且此时的binCount表示为链表长度
                        //2 如果发生写冲突 bincount-1表示冲突位置 
                        binCount = 1;
                        //自旋
                        for (Node<K,V> e = f;; ++binCount) {
K ek;
 //如果当前节点的hash和key与要插入节点的hash和key相同,说明有写冲突,把当前节点的value值赋值给oldValue
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek))))         
                                oldVal = e.val;