文章目录
前言
咱们话接前文,今天继续C~Map的剖根问底,有人在后台私信cas等的问题,后边会有专门的文章来分享这些知识点,毕竟每篇文章都要有自己的主题。不在主题里边的分享起来可能没完没了,导致主次错乱。还是附上我们的上一节文章连接:金三银四之ConcurrentHashMap剖根问底栏目(一)。
1、ConcurrentHashMap的get方法
get方法因为是取值,熟悉多线程编程我们知道,大部分情况下读是不用加锁的,C-Map也没例外,get时并没有加锁,如果我们稍微多问个为什么,就会想到这个C~Map的get方法为什么没有从treenode节点获取数据,看我们下边的注释e.find方法其实就是关键。
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//算出key的hash
int h = spread(key.hashCode());
//取出table,然后根据路由算法,取出存该key的node节点
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//如果取出来的节点key的hash与当前key的hash相等,且值也相等
//那么就是我们要找的值了
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
//
else if (eh < 0)
//这里是一个关键点,我们会发现为什么C~Map的get方法会没有
//treenode节点的查询呢,其实是treenode继承了node节点,
//也重写了find方法,所以这里直接调用find方法就行。
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
2、ConcurrentHashMap的keyset方法
keyset也是常用的方法之一,获取到map中的key的集合,返回值和HashMap的不同,C-Map返回的是KeySetView对象,其源码及重要说明如下所示:
public KeySetView<K,V> keySet() {
KeySetView<K,V> ks;
//返回的是KeySetView,如果当前的keySet为空,那么就new一个
//看下面的静态内部类KeySetView
return (ks = keySet) != null ? ks : (keySet = new KeySetView<K,V>(this, null));
}
public static class KeySetView<K,V> extends CollectionView<K,V,K>
implements Set<K>, java.io.Serializable {
KeySetView(ConcurrentHashMap<K,V> map, V value) { // non-public
super(map);
this.value = value;
}
//省略部分代码
//lambda表达式
public void forEach(Consumer<? super K> action) {
if (action == null) throw new NullPointerException();
Node<K,V>[] t;
if ((t = map.table) != null) {
//如果看Traverser的实现会发现,内部实现了一个for循环迭代器去
//遍历所有的node信息
Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
for (Node<K,V> p; (p = it.advance()) != null; )
//把key给到Consumer接口参数里
//这样我们使用的时候就能拿到key了
action.accept(p.key);
}
}
}
3、ConcurrentHashMap中被使用频次非常高的tabAt方法
通过看源代码我们会发现该方法的主要作用就是:首先i为key的hash计算后的值,ASHIFT和ABASE在金三银四之ConcurrentHashMap剖根问底栏目(一)这篇文章中介绍过,获取key在tab中的偏移量,而计算偏移量的方法就是 i<< ASHIFT + ABASE, 剩下就好说了该句的意思就是:从主内存中获取存放该key的node。
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
4、ConcurrentHashMap的helpTransfer方法
该方法主要作用就是当节点是MOVED节点时就会调用这个方法。如果不懂compareAndSwapInt方法请点击Unsafe类常见方法精讲
进行预习
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
//如果当前的节点不为空、节点时ForwardingNode节点
//且下个节点也不为空
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
//该方法是在tab的当前长度下生成扩容标识戳
int rs = resizeStamp(tab.length);
//sizeCtl小于0代表正在扩容。
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
//cas成立之后就进行移动,其实这个方法很有讲究
//SIZECTL偏移后的位置本身记录的就是sizeCtl的值
//sc在上边一步又被赋值为sizeCtl
//所以在这一步才能进行cas,要不就根本没有什么意义
//cas本身是原子操作,直接将this的sizeCtl的值+1
//这样在下次循环过来的时候会继续+1,只要不超过MAX_RESIZERS(65535)最大扩容数
//
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
5、结语
道阻且长,行则将至,行而不辍,未来可期,加油。
原创不易,如果你觉得文章不错,对你的进步有那么一点帮助,那么就给个小心心,如果觉得文章非常对你的胃口,那么欢迎你关注我,或者关注个人的微信公众号 程序猿每日分享,这里有资源,有内推,有和你志同道合的朋友,咱们一起打怪升级。