我读过Oracle's documentation,其中指出(除其他事项外):
读写是引用变量的原子
因此,这意味着,假设我正确理解,以下代码是线程安全的,而无需volatile
,synchronized
或使用Lock
类,因为otherHashlist
对hashlist
的实际分配是原子的,从hashlist
到tempHashlist
的分配也是原子的。
public class SomeClass
{
private HashMap<Integer, ArrayList<MyObject>> hashlist = null;
// ...Thread/Runnable logic to periodically call set()...
private void set()
{
HashMap<Integer, ArrayList<MyObject>> otherHashlist = new HashMap<Integer, ArrayList<MyObject>>();
// ...populate otherHashlist...
hashlist = otherHashlist;
}
public ArrayList<MyObject> get(int i)
{
HashMap<Integer, ArrayList<MyObject>> tempHashlist = hashlist;
return new ArrayList<MyObject>(tempHashlist.get(i));
}
}
此外,绝不能以任何方式访问
hashlist
和get()
之外的set()
。该类以外的任何内容也不能直接或间接调用set()
。 get()
返回的ArrayList是new
,因此修改ArrayList的操作(set(),remove()等)将不会影响hashlist
中的原始ArrayList。我还没有在MyObject
上定义任何setter方法,它们的所有成员变量都是private final int
,private final long
或private final String
。因此,我的问题是:这段代码实际上是线程安全的吗?还是有一些我正在做的假设/我想念的角度会使这不安全?
最佳答案
这不是线程安全的,因为尽管设置字段是安全的并且不会导致重叠的更新,但是其他线程实际上可能看不到更改。也就是说,即使Thread1
将hashlist
设置为某种值,Thread2
也可能看不到该更改。这是因为允许JVM优化对hashlist
中的Thread2
的访问,例如将引用复制到寄存器中,因此无需多次执行getfield
。在这种情况下,当Thread1
更改hashlist
时,Thread2
无法看到它,因为它不再使用该字段,而是使用它的本地副本,它认为是该字段。 This question显示在这种情况下可能发生的情况。
最简单的方法是标记hashlist
volatile
,这意味着一个线程中的更改实际上可以被其他线程看到。