今天本来是想要优化下自己的OTA代码,使得效率更高,结果被现实赤裸裸打了脸,我来为大家科普一下关于concurrenthashmap有几个分段?下面希望有你要的答案,我们一起来看看吧!

concurrenthashmap有几个分段(ConcurrentHashMapRESERVED状态死循环)

concurrenthashmap有几个分段

背景

今天本来是想要优化下自己的OTA代码,使得效率更高,结果被现实赤裸裸打了脸。

过程

如下是原先的代码:

private static final Map<String, Object> holder = new ConcurrenthashMap<>(16); if (holder.get(clazzName) == null) { holder.putIfAbsent(clazzName, createSingleton(clazz, params)); } return (T) holder.get(clazzName);

如上代码是笔者一个单例创建工具的一部分代码,今天在浏览Map接口的时候,发现了几个方法:

default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction); default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction); default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);

很好,通过这几个方法,可以完美的吧老代码替换为如下代码:

return (T) holder.computeIfAbsent(clazzName, key -> createSingleton(clazz, params));

一句话搞定,笔者美滋滋的在更改完毕后运行程序,然后发现了一个非常坑爹的现象:笔者的应用阻塞在某个点上,一致无法执行下去。

调试

一开始,笔者以为是自己的单例创建工具除了问题。

因为,这个单例工具参照了IOC的设计,将对象存储到容器内。如果有调用方通过类以及构造实参调用,就会判断容器内是否存在,如果存在则直接返回对象、不存在则通过类以及构造实参创建一个实例返回。

然后在单步调试的过程中,发现竟然卡在了holder.computeIfAbsent这个方法上,那就step into进去看看是什么原因导致的问题。

最终经过一系列调试,将问题定位在了如下的代码片段:

else { boolean added = false; synchronized (f) { if (tabAt(tab, i) == f) { if (fh >= 0) { binCount = 1; for (Node<K,V> e = f;; binCount) { K ek; V ev; if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { val = e.val; break; } Node<K,V> pred = e; if ((e = e.next) == null) { if ((val = mappingFunction.apply(key)) != null) { added = true; pred.next = new Node<K,V>(h, key, val, null); } break; } } } else if (f instanceof TreeBin) { binCount = 2; TreeBin<K,V> t = (TreeBin<K,V>)f; TreeNode<K,V> r, p; if ((r = t.root) != null && (p = r.findTreeNode(h, key, null)) != null) val = p.val; else if ((val = mappingFunction.apply(key)) != null) { added = true; t.putTreeVal(h, key, val); } } } } if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (!added) return val; break; } }

这是ConcurrentHashMap,在获取key所对应的桶(ConcurrentHashMap内部一维数组桶 链表(红黑树)结构)的过程,如果桶不为空且不在扩容的过程中,则会执行上述这段代码。

这段代码肯定没啥问题,那为啥会导致一直在死循环,而没法进入某个具体的执行过程呢(fh一直小于0,且不是红黑树节点)?

仔细看了下后,笔者意外的发现,所获取到的节点竟然是一个暂存态节点。什么叫暂存态,即这个节点没有真正初始化完毕,节点的状态为RESERVED(-3)。

定位到原因:容器内存在正在初始化的节点,好巧不巧的是,另外有调用方使用computeIfAbsent方法,传入的keyhash后的节点正好是初始化的节点,那就悲剧了。后面的方法需要等待这个节点初始化完毕后才会执行成功,否则将永远处于while死循环的状态。而笔者的这种情况就是最坏的情况,不知道什么原因导致前者初始化一直完成不了。

解决

好了,定位到了问题所在,现在的关注点在于,为什么会出现前者一致处于初始化的情况?

笔者在获取单例的方法上做了断点,每次调用后,都查看下容器内的table属性是否存在初始化的节点。然后发现了某个对象(A)在获取单例后,没有初始化完成,而又在构造方法内进行了单例类(B)的获取。好巧不巧,A和B的key在hash后的桶索引是一样的,也就造成了A对象在等初始化完成,初始化内的B对象在初始化过程中,由于hash冲突,等待节点的初始化完成,完美的形成了死循环。

疑问点

问题以及原因虽然找到了,但是为啥之前的写法会没有问题呢?之前也是使用了ConcurrentHashMap作为对象存储的容器,按理也会出现这个问题。仔细看了看代码后,笔者恍然,之前的问题所在主要是computeIfAbsent在调用过程中被阻塞(调用过程中做了对象的创建)。而老的代码是:在创建成功单例类之前,是不存在map.put的过程,自然也不会出现前者的情况。

,