我读过Oracle's documentation,其中指出(除其他事项外):


  读写是引用变量的原子


因此,这意味着,假设我正确理解,以下代码是线程安全的,而无需volatilesynchronized或使用Lock类,因为otherHashlisthashlist的实际分配是原子的,从hashlisttempHashlist的分配也是原子的。

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));
  }
}


此外,绝不能以任何方式访问hashlistget()之外的set()。该类以外的任何内容也不能直接或间接调用set()get()返回的ArrayList是new,因此修改ArrayList的操作(set(),remove()等)将不会影响hashlist中的原始ArrayList。我还没有在MyObject上定义任何setter方法,它们的所有成员变量都是private final intprivate final longprivate final String

因此,我的问题是:这段代码实际上是线程安全的吗?还是有一些我正在做的假设/我想念的角度会使这不安全?

最佳答案

这不是线程安全的,因为尽管设置字段是安全的并且不会导致重叠的更新,但是其他线程实际上可能看不到更改。也就是说,即使Thread1hashlist设置为某种值,Thread2也可能看不到该更改。这是因为允许JVM优化对hashlist中的Thread2的访问,例如将引用复制到寄存器中,因此无需多次执行getfield。在这种情况下,当Thread1更改hashlist时,Thread2无法看到它,因为它不再使用该字段,而是使用它的本地副本,它认为是该字段。 This question显示在这种情况下可能发生的情况。

最简单的方法是标记hashlist volatile,这意味着一个线程中的更改实际上可以被其他线程看到。

10-06 02:50