一、put操作核心步骤
 1、判断key或value是否为null,是则抛出空指针异常,即ConcurrentHashMap中不允许null作为key或value
 2、计算key的hash值,hash值的计算方法:先对key的hashCode无符号右移16位,再和key的hashCode进行异或运算,最后和int的最大值进行与运算

static final int HASH_BITS = 0x7fffffff;
static final int spread(int h) {
	return (h ^ (h >>> 16)) & HASH_BITS;
}

 3、判断ConcurrentHashMap对象的table(是一个Node<k,v>[]结构的数组)是否初始化,没有则初始化该数组
 4、若table属性已经初始化,再据key的hash值判断对应table数组位置是否已有元素(是一个Node)存在,若不存在则通过CAS将当前操作的<k,v>对包装成一个Node置于这个数组的对应位置,作为链表的第一个节点。此时,这个<k,v>就已经成功put到map中了,接着会判断是否需要扩容,至此,此种情况下的put操作就结束了
 5、如果4中判断hash对应的table数组的位置上已经存在Node节点(即首节点已经存在,但不一定仅有一个首节点,有可能已经有多个)了,会再判断这个首节点是否被标记为扩容状态(首节点的hash值是否被置为-1,即MOVED状态),若被置为了MOVED状态,则说明该ConcurrentHashMap对象正在进行扩容操作,则当前线程会参与到ConcurrentHashMap的扩容中

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

 6、如果首节点没有被标记为扩容状态,则当前线程会使用synchronized关键字锁住首节点
 7、锁住首节点之后的第一个操作是再次判断key的hash对应的数组位置的元素是否为6中被锁住的首节点(避免多线程操作的情况下发生安全问题,比如A线程将要执行对首节点的上锁操作时B线程将首节点删除了,这个时候A线程再上锁就起不到作用了),如果已经不是被锁住的首节点则会从3开始进行自旋

synchronized (f) {
	if (tabAt(tab, i) == f) {
		// 执行链表追加操作等
	}
}

 8、若7中判断锁住的还是首节点,则接着判断首节点的hash值是否大于等于0(当hash值大于等于0时是链表结构,当hash值为-2时是红黑树结构),也就是判断table的该位置是否已经由链表转化为红黑树,如果是链表结构,再从链表的首节点依次向后比对链表中各个节点和当前操作的k是否相同(hash相同并且key相等或者equals比较相等),若是的话则对原链表节点的值进行覆盖,若所有的节点都比对之后没有key与当前操作的k相同则将当前操作的<k,v>包装成一个Node追加到链表的末尾,在这个过程中会记录链表的长度,然后会判断链表的长度是否达到转化为红黑树的长度,若达到了则将链表转为红黑树

for (Node<K,V> e = f;; ++binCount) {
	K ek;
	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;
	}
}

 9、若8中当前table的位置已经是红黑树,则直接将首节点强转为一个TreeBin对象,并调用其添加树节点的方法把我们操作的<k,v>放在红黑树中

else if (f instanceof TreeBin) {
	Node<K,V> p;
	binCount = 2;
	if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
		oldVal = p.val;
		if (!onlyIfAbsent)
			p.val = value;
	}
}

参看:ConcurrentHashMap详解