易失性数组 - 元素的内存可见性

时间:2014-06-23 19:52:49

标签: java arrays thread-safety volatile memory-visibility

考虑代码段

class A {

   private Map<String, Object> taskMap = new HashMap<>();
   private volatile Object[] tasksArray ;

   // assume this happens on thread1 
   public void assignTasks() {
     synchronized(taskMap){
         // put new tasks into map
         // reassign values from map as a new array to tasksArray ( for direct random access )
     }

    }

   // assume this is invoked on Thread2
   public void action(){
       int someindex =  <init index to some value within tasksArray.length>;
       Object[] localTasksArray = tasksArray;
       Object oneTask = localTasksArray[someindex];
       // Question : is the above operation safe with respect to memory visibility for Object oneTask ?
       // is it possible that oneTask may appear null or in some other state than expected ?

   }

}

问题:关于对象oneTask的内存可见性,操作Object oneTask = localTasksArray[someindex];是否安全? 是否有可能oneTask可能显示为null或处于某种其他状态而不是预期?

我的想法是:

thread2可能会将oneTask视为null或处于某种状态而非预期状态。这是因为,即使taskArrayvolatile,并且读取此数组将确保数组本身的正确可见性,这也不能确保对象内部状态的可见性{{1 }}

3 个答案:

答案 0 :(得分:4)

volatile关键字仅保护作为Object []引用的字段taskArray。无论何时读取或写入此字段,它都将具有一致的排序。但是,这不会扩展到引用的数组,也不会扩展到数组引用的对象。

很可能你想要AtomicReferenceArray。

答案 1 :(得分:3)

据我记忆(我现在正在研究确认),Java只保证将volatile对象本身刷新到RAM,而不是该对象的子部分(如在数组条目中)或sub - 对象的字段(在对象的情况下)。

但是 - 我相信大多数(如果不是全部)JVM都将volatile访问权作为内存屏障实现(请参阅here和引用的页面The JSR-133 Cookbook for Compiler Writers)。因此,作为内存屏障,这意味着当访问发生时,其他线程的所有先前写入将从缓存刷新到主内存 - 从而使所有内存在此时保持一致。

这不应该依赖 - 如果您完全控制可见内容和不可见内容至关重要,则应使用其他人Atomic之类的AtomicReferenceArray类之一。出于这个原因,这些甚至可能比volatile更有效。

答案 2 :(得分:1)

声明字段volatile ensures,该变量的任何写入与此值的任何后续(根据同步顺序)同步(并因此可见)。请注意,不存在易失性对象或易失性数组。只有易失性字段

因此,在你的代码中,在将一个对象存储到数组中的Thread 1与从那里读取它的Thread 2之间没有发生之前的关系。