钓鱼执法
为什么我在文章的一开始就说了这是 Doug Lea 在钓鱼执法呢?
因为在最开始提问的艺术那一部分,我相信,Doug Lea 跑完那个测试案例之后,心里也有点数了。
大概知道问题在哪了,而且从他的回答和他写的文档中我也有理由相信,他写的这个方法的时候就知道可能会出问题。
而且,Pardeep 的回复中提到了文档,那我们就去看看官方文档对于该方法的描述是怎样的:
https://docs.oracle.com/javase/8/docs/api/
文档中说函数方法应该简短,简单。而且不能在更新的映射的时候更新映射。就是说不能套娃。
套娃,用程序说就是recursive(递归),按照文档说如果存在递归,则会抛出 IllegalStateException 。
而提到递归,你想到了什么?
我首先就想到了斐波拉契函数。我们用 computeIfAbsent 实现一个斐波拉契函数如下:
public class Test { static Map<Integer, Integer> cache = new ConcurrentHashMap<>(); public static void main(String[] args) { System.out.println("f(" + 14 + ") =" + fibonacci(14)); } static int fibonacci(int i) { if (i == 0) return i; if (i == 1) return 1; return cache.computeIfAbsent(i, (key) -> { System.out.println("Slow calculation of " + key); return fibonacci(i - 2) + fibonacci(i - 1); }); } }
这就是递归调用,我用 JDK 1.8 跑的时候并没有抛出 IllegalStateException,只是程序假死了,原因和我们前面分析的是一样一样的。我理解这个地方是和文档不符的。
所以,我怀疑是 Doug Lea 在这个地方钓鱼执法。
CHM一定线程安全吗?
既然都说到 currentHashMap(CHM)了,那我说一个相关的注意点吧。
首先 CHM 一定能保证线程安全吗?
是的,CHM 本身一定是线程安全的。但是,如果你使用不当还是有可能会出现线程不安全的情况。
给大家看一点 Spring 中的源码吧:
org.springframework.core.SimpleAliasRegistry
在这个类中,aliasMap 是 ConcurrentHashMap 类型的:
在 registerAlias 和 getAliases 方法中,都有对 aliasMap 进行操作的代码,但是在操作之前都是用 synchronized 把 aliasMap 锁住了。
为什么?为什么我们操作 ConcurrentHashMap 的时候还要加锁呢?
这个是根据场景而定的,这个别名管理器,在这里加锁应该是为了避免多个线程操作 ConcurrentHashMap 。
虽然 ConcurrentHashMap 是线程安全的,但是假设如果一个线程 put,一个线程 get,在这个代码的场景里面是不允许的。
如果觉得不太好理解的话我举一个 redis 的例子。
redis 的 get、set 方法都是线程安全的吧。但是你如果先 get 再 set,那么在多线程的情况下还是会有问题的。
因为这两个操作不是原子性的。所以 incr 就应运而生了。
我举这个例子的是想说线程安全与否不是绝对的,要看场景。给你一个线程安全的容器,你使用不当还是会有线程安全的问题。
再比如,HashMap 一定是线程不安全的吗?
说不能说的这么死吧。它是一个线程不安全的容器。但是如果我的使用场景是只读呢?
在这个只读的场景下,它就是线程安全的。
总之,看场景。道理,就是这么一个道理。
最后说两句(求关注)
所以点个“赞”吧,周更很累的,不要白嫖我,需要一点正反馈。
才疏学浅,难免会有纰漏,如果你发现了错误的地方,还请你留言指出来,我对其加以修改。
感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。
我是 why,一个被代码耽误的文学创作者,不是大佬,但是喜欢分享,是一个又暖又有料的四川好男人。
欢迎关注我的微信公众号:why技术。在这里我会分享一些java技术相关的知识,用匠心敲代码,对每一行代码负责。偶尔也会荒腔走板的聊一聊生活,写一写书评、影评。感谢你的关注,愿你我共同进步。