多个线程同时读一个HashMap,有一条线程会定时获取最新的数据,构建新的HashMap并将旧的引用指向新的HashMap,这是一种相似CopyOnWrite(写时复制)的写法,主要代码以下,在不要求数据实时更新(容忍数据不是最新),而且各个线程之间容忍看不到对方的最新数据的状况下,这么这种写法安全吗?html
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后才是安全的java
不少资料都会介绍volatile是易失性的,各个线程能够实时看到volatile变量的值,这种解释虽然没错可是不完整,容易误导开发人员(包括本文遇到的问题),同时这种解释没有深刻到JVM的happen-before,建议你们少看这种解释安全
JVM的会对指令进行优化重排,虽然书写顺序是A先于B,但可能执行结果是B先于A,要避免这种问题就要用到happen-beforeapp
happen-before有如下八大原则:优化
- 单线程happen-before原则:在同一个线程中,书写在前面的操做happen-before后面的操做。
- 锁的happen-before原则:同一个锁的unlock操做happen-before此锁的lock操做。
- volatile的happen-before原则:对一个volatile变量的写操做happen-before对此变量的任意操做(固然也包括写操做了)。
- happen-before的传递性原则:若是A操做 happen-before B操做,B操做happen-before C操做,那么A操做happen-before C操做。
- 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
- 线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
- 线程终结的happen-before原则:线程中的全部操做都happen-before线程的终止检测。
- 对象建立的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。
被声明为volatile的变量知足happen-before原则,对此变量的写操做老是happen-before于其余操做,因此才会出现其余文章关于volatile对其余线程可见的解释,可见是表象,happen-before才是缘由this
在本文的问题中,若是没有volatile, 不知足happen-before的原则,JVM会对指令进行重排,cache = newcache可能先于newcache.put("key", "value"),若是此时其余线程读取了HashMap,就会找不到数据,换句话说这种写法是线程不安全的.atom
对于线程问题,水平不够或者理解不够深刻,仍是乖乖用JDK提供的实现类,这些类都是通过千锤百炼出来的,远远没有看上去简单spa