Bean中的CHM变动是否需volatile关键字确保线程安全?
摘要:你好呀,我是歪歪。 事情是这样的,前几天有一个读者给我发消息,说他面试的时候遇到一个奇形怪状的面试题。 歪师傅纵横面试界多年,最喜欢的是奇形怪状的面试题。 可以说是见过大场面的人,所以让他描述一下具体啥问题。 据他的描述,这道面试题是这样的
你好呀,我是歪歪。
事情是这样的,前几天有一个读者给我发消息,说他面试的时候遇到一个奇形怪状的面试题。
歪师傅纵横面试界多年,最喜欢的是奇形怪状的面试题。
可以说是见过大场面的人,所以让他描述一下具体啥问题。
据他的描述,这道面试题是这样的:
在多线程环境下使用 ConcurrentHashMap 时,是否需要将其声明为 volatile 以确保线程安全?
呃...
这个题...
有点意思...
简单盘一盘
这个题听起来确实有点奇奇怪怪的,多线程、ConcurrentHashMap(后续文中用 CHM 代替)、volatile、线程安全...
乍一听有一种全都是我熟悉的技术点,但是组合在一起,突然有点不认识了的陌生感。
但是如果你真的对上面这几个技术点达到了熟悉的程度,那么简单梳理一下之后,你又会觉得线索太多,甚至有点不知道从何说起。
先梳理清楚两个关键点:
CHM 是干啥的?volatile 又是干啥的?
首先,CHM 是八股中老大哥了,一般它和 HashMap 会在面试环节成对出现。
比如这样式儿的:HashMap 不是线程安全的,那我们应该怎么办呢?
然后 CHM 就噼里啪啦一大堆开始背诵起来了。
但是在这篇文章中,关于 CHM 我们需要注意的就一个点:
CHM 是线程安全的,但是它的线程安全仅限于方法内部的操作。
然后,volatile 是干啥的?
这种老八股应该是张口就来:
volatile 可以保证变量的可见性,即一个线程修改了被 volatile 修饰的变量,其他线程能立即看到新值。
这里画个重点:变量。
如果要把 CHM 和 volatile 牵扯到一起,那么他们就需要对齐一下颗粒度:CHM 需要是一个变量。
当 CHM 在程序的引用中会发生变化时,讨论 volatile 才有意义。
所以,这个面试题的答案也就呼之欲出了。
答案就是:得结合代码,看具体场景,分两种情况去讨论。
第一种情况是 CHM 的引用不会发生变化,就不需要加 volatile。
比如下面这种写法:
private static final ConcurrentHashMap<String, String> chm = new ConcurrentHashMap<>();
这里的 final 已经保证引用不可变,无论多少个线程在同时操作这个 CHM,都能确保看到的始终是同一个对象。
至于线程安全问题,CHM 内部的方法自会处理好并发问题。
第二种情况是 CHM 的引用会变,比如这样的代码逻辑:
privatevolatileConcurrentHashMap<String,String>cache=newConcurrentHashMap<>();
publicvoidupdateCache(){
ConcurrentHashMap<String,String>newCache=newConcurrentHashMap<>();
//填充新数据...
cache=newCache;
}
因为我们程序中有把 newCache 赋值给 cache 的操作,而这个 cache 可能又不只是一个线程在操作。
所以这个场景下,就需要使用 volatile,保证当前线程对 cache 操作之后,其他线程能立刻看到新引用。
从而保证了线程安全。
现在,我们再回过头去看这个问题,应该就清晰很多了:
在多线程环境下使用 ConcurrentHashMap 时,是否需要将其声明为 volatile 以确保线程安全?
这个问题确实是有点陷阱的,不能直接回答需要或者不需要。
需要面试者结合自己的理解,去分析出不同的场景,得到上面的回答。
想到另一个题
在分析上面这个问题的时候,我又联想到了另外一个经典的面试题。
问:Spring 的 Bean 是否是线程安全的?
我个人认为这两个题的相似程度算是非常高的。
为啥这样说呢?
我们一起分析一波。
首先,我们搞个示意代码:
@Controller
publicclassTestController{
privateintnum=0;
@RequestMapping("/test")
publicvoidtest(){
System.out.println(++num);
}
@RequestMapping("/test1")
publicvoidtest1(){
System.out.println(++num);
}
}
TestController 就是在 Spring 中托管的一个 Bean。
