3

考虑代码片段

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 ?

   }

}

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

我的想法是:

thread2 可能会oneTask被视为 null 或处于预期之外的某种状态。这是因为,即使taskArrayvolatile,并且对该数组的读取将确保数组本身的正确可见性,但这并不能确保对象内部状态的可见性oneTask

4

3 回答 3

4

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

很可能您想要 AtomicReferenceArray 代替。

于 2014-06-23T20:08:39.663 回答
3

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

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

不应依赖这一点 - 如果您必须完全控制什么是可见的,什么是不可见的,那么您应该使用其他人建议的Atomic类之一。AtomicReferenceArray这些甚至可能比volatile正是这个原因更有效。

于 2014-06-23T20:16:40.440 回答
1

声明一个 volatile 字段可确保对该变量的任何写入与该值的任何后续(根据同步顺序)同步(因此可见)。请注意,没有 volatile对象或 volatile数组之类的东西。只有 volatile字段

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

于 2014-06-23T20:13:24.887 回答