考虑代码段
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或处于某种状态而非预期状态。这是因为,即使taskArray
是volatile
,并且读取此数组将确保数组本身的正确可见性,这也不能确保对象内部状态的可见性{{1 }}
答案 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之间没有发生之前的关系。