多个线程同时读一个HashMap,有一条线程会定时获取最新的数据,构建新的HashMap并将旧的引用指向新的HashMap,这是一种类似CopyOnWrite(写时复制)的写法,主要代码如下,在不要求数据实时更新(容忍数据不是最新),并且各个线程之间容忍看不到对方的最新数据的情况下,这么这种写法安全吗?
public class myClass { private HashMap<String,String> cache = null; public void init() { refreshCache(); } // this method can be called occasionally to update the cache. public void refreshCache() { HashMap<String,String> newcache = new HashMap<String,String>(); newcache.put("key", "value"); // code to fill up the new cache // and then finally cache = newcache; //assign the old cache to the new one in Atomic way } } 复制代码
这种写法并不安全,HashMap需要声明为volatile后才是安全的
很多资料都会介绍volatile是易失性的,各个线程可以实时看到volatile变量的值, 这种解释虽然没错但是不完整,容易误导开发人员(包括本文遇到的问题) ,同时这种解释没有深入到JVM的happen-before,建议大家少看这种解释
JVM的会对指令进行优化重排,虽然书写顺序是A先于B,但可能执行结果是B先于A,要避免这种问题就要用到happen-before
happen-before有以下八大原则:
被声明为volatile的变量满足happen-before原则,对此变量的写操作总是happen-before于其他操作,所以才会出现其他文章关于volatile对其他线程可见的解释, 可见是表象,happen-before才是原因
在本文的问题中,如果没有volatile, 不满足happen-before的原则, JVM会对指令进行重排,cache = newcache可能先于newcache.put("key", "value") ,如果此时其他线程读取了HashMap,就会找不到数据,换句话说这种写法是线程不安全的.