【collection】2.java容器之HashMap&LinkedHashMap&Hashtable2

ConcurrentHashMap

put操作

final V putVal(K key, V value, boolean onlyIfAbsent) {
	if (key == null || value == null) throw new NullPointerException();
	// 本质上和hashmap没有什么差别,都是把hashcode进行对半异或,这样就可以用一半的位数,集合了32位长度的信息
	int hash = spread(key.hashCode());
	int binCount = 0;
	Node<K,V>[] tab = table;
	// 这里循环的目的是cas的重试操作
	for (;;) {
		// 指向待插入元素应当插入的位置
		Node<K,V> f;
		// 元素f对应的哈希值
		int fh;
		// 当前hash表数组的长度容量
		int n;
		int i;
		// 如果哈希数组还未初始化,或者容量无效,则需要初始化一个哈希数组
		if (tab == null || (n = tab.length) == 0) {
			tab = initTable();
			// 这里n -1 & hash 是经典取余操作,参考之前hashmap
		} else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
			// 如果当前位置是空的,那么就可以通过cas设置对应的值
			Node<K, V> newNode = new Node<K,V>(hash, key, value, null);
			// 用一次 CAS 操作将这个新值放入其中即可,这个 put 操作差不多就结束了,可以拉到最后面了
			// 如果 CAS 失败,那就是有并发操作,进到下一个循环就好了
			if (casTabAt(tab, i, null, newNode)) {
				// 插入完成,跳出循环,如果更新失败,重新循环进入
				break;                   // no lock when adding to empty bin
			}
		} else if ((fh = f.hash) == MOVED) {
			/*
			 * 如果待插入元素所在的哈希槽上已经有别的结点存在,且该结点类型为MOVED
			 * 说明当前哈希数组正在扩容中,此时,可以尝试加速扩容过程
			 */
			tab = helpTransfer(tab, f);
		} else {
			V oldVal = null;
			// 这里避免并发,对f节点的引用进行上锁
			synchronized (f) {
				// 如果tab[i]==f,则代表当前待插入状态仍然可信
				if (tabAt(tab, i) == f) {
					// fh > 0 标识不是在扩容,是正常节点
					if (fh >= 0) {
						binCount = 1;
						// 遍历链表
						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;
							}
						}
					} 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;
						}
					}
				}
			}
			// 如果对已有元素搜索过,则计数会发生变动,这里需要进一步观察
			if (binCount != 0) {
				// 哈希槽(链)上的元素数量增加到TREEIFY_THRESHOLD后,这些元素进入波动期,即将从链表转换为红黑树
				if (binCount >= TREEIFY_THRESHOLD) {
					// 注意,这里也只是锁了这一个节点,注意,这里不一定一定是转换为红黑树
					// 如果整个tab长度是小于64的话,这里会选择自动扩容,如果已经超过64了,才考虑转换红黑树
					treeifyBin(tab, i);
				}
				if (oldVal != null)
					return oldVal;
				break;
			}
		}
	}
	addCount(1L, binCount);
	return null;
}

get和remove操作

略,就是根据索引找节点

扩容

核心方法就是transfer

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
	int n = tab.length;
	// stride 在单核下直接等于 n,多核模式下为 (n>>>3)/NCPU,最小值是 16
	//   将这 n 个任务分为多个任务包,每个任务包有 stride 个任务
	int stride = (NCPU > 1) ? (n >>> 3) / NCPU : n;
	if (stride < MIN_TRANSFER_STRIDE) {
		stride = MIN_TRANSFER_STRIDE; // subdivide range
	}
	if (nextTab == null) {            // initiating
		try {
			// 直接扩大一倍
			@SuppressWarnings("unchecked")
			Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
			nextTab = nt;
		} catch (Throwable ex) {      // try to cope with OOME
			sizeCtl = Integer.MAX_VALUE;
			return;
		}
		nextTable = nextTab;
		transferIndex = n;
	}
	int nextn = nextTab.length;

	// ForwardingNode 翻译过来就是正在被迁移的 Node
	// 这个构造方法会生成一个Node,key、value 和 next 都为 null,关键是 hash 为 MOVED
	// 后面我们会看到,原数组中位置 i 处的节点完成迁移工作后,
	//    就会将位置 i 处设置为这个 ForwardingNode,用来告诉其他线程该位置已经处理过了
	//    所以它其实相当于是一个标志。, 这个在put的时候会判断节点hash值,用来判断是否需要协助扩容
	ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
	// advance 指的是做完了一个位置的迁移工作,可以准备做下一个位置的了
	boolean advance = true;
	boolean finishing = false; // to ensure sweep before committing nextTab
	// 循环所有的节点位置,判断是否需要进行迁移
	for (int i = 0, bound = 0;;) {
		Node<K,V> f; int fh;
		while (advance) {
			int nextIndex, nextBound;
			if (--i >= bound || finishing) {
				advance = false;
			} else if ((nextIndex = transferIndex) <= 0) {
				i = -1;
				advance = false;
			} else if (U.compareAndSwapInt
					 (this, TRANSFERINDEX, nextIndex,
					  nextBound = (nextIndex > stride ?
								   nextIndex - stride : 0))) {
				bound = nextBound;
				i = nextIndex - 1;
				advance = false;
			}
		}
		if (i < 0 || i >= n || i + n >= nextn) {
			int sc;
			if (finishing) {
				nextTable = null;
				table = nextTab;
				sizeCtl = (n << 1) - (n >>> 1);
				return;
			}
			if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
				if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
					return;
				finishing = advance = true;
				i = n; // recheck before commit
			}
		}
		else if ((f = tabAt(tab, i)) == null)
			advance = casTabAt(tab, i, null, fwd);
		else if ((fh = f.hash) == MOVED)
			advance = true; // already processed
		else {
			// 真正的开始数据迁移,先对f节点上锁,f是tab中的一个位置
			synchronized (f) {
				// 保证数据正确性没有发生变化
				if (tabAt(tab, i) == f) {
					Node<K,V> ln, hn;
					// 头节点的 hash 大于 0,说明是链表的 Node 节点
					if (fh >= 0) {
						// 下面这一块和 Java7 中的 ConcurrentHashMap 迁移是差不多的,
						// 需要将链表一分为二,
						// 找到原链表中的 lastRun,然后 lastRun 及其之后的节点是一起进行迁移的
						// lastRun 之前的节点需要进行克隆,然后分到两个链表中
						int runBit = fh & n;
						Node<K,V> lastRun = f;
						// 循环链表,获取到尾节点
						for (Node<K,V> p = f.next; p != null; p = p.next) {
							int b = p.hash & n;
							if (b != runBit) {
								runBit = b;
								lastRun = p;
							}
						}
						if (runBit == 0) {
							ln = lastRun;
							hn = null;
						} else {
							hn = lastRun;
							ln = null;
						}
						// 从头循环到尾部
						for (Node<K,V> p = f; p != lastRun; p = p.next) {
							int ph = p.hash;
							K pk = p.key;
							V pv = p.val;
							// ph是这个节点的hash值&n如果为0,说明再n-1部分,位置没变,还是放入之前的位置
							if ((ph & n) == 0) {
								ln = new Node<K,V>(ph, pk, pv, ln);
							} else {
								// 不为0,说明再n-1上面的位置,位置变了,那么就重新设置位置
								hn = new Node<K,V>(ph, pk, pv, hn);
							}
						}
						// 其中的一个链表放在新数组的位置 i
						setTabAt(nextTab, i, ln);
						// 把head放到数组i+n的位置
						setTabAt(nextTab, i + n, hn);
						// 将原数组该位置处设置为 fwd,代表该位置已经处理完毕,
						//    其他线程一旦看到该位置的 hash 值为 MOVED,就不会进行迁移了
						setTabAt(tab, i, fwd);
						advance = true;
					} else if (f instanceof TreeBin) {
						// 红黑树的迁移
						TreeBin<K,V> t = (TreeBin<K,V>)f;
						TreeNode<K,V> lo = null, loTail = null;
						TreeNode<K,V> hi = null, hiTail = null;
						int lc = 0, hc = 0;
						for (Node<K,V> e = t.first; e != null; e = e.next) {
							int h = e.hash;
							TreeNode<K,V> p = new TreeNode<K,V>
								(h, e.key, e.val, null, null);
							if ((h & n) == 0) {
								if ((p.prev = loTail) == null)
									lo = p;
								else
									loTail.next = p;
								loTail = p;
								++lc;
							}
							else {
								if ((p.prev = hiTail) == null)
									hi = p;
								else
									hiTail.next = p;
								hiTail = p;
								++hc;
							}
						}
						ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
							(hc != 0) ? new TreeBin<K,V>(lo) : t;
						hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
							(lc != 0) ? new TreeBin<K,V>(hi) : t;
						setTabAt(nextTab, i, ln);
						setTabAt(nextTab, i + n, hn);
						setTabAt(tab, i, fwd);
						advance = true;
					}
				}
			}
		}
	}
}
posted @ 2022-12-06 10:11  cutter_point  阅读(14)  评论(0编辑  收藏  举报