Java 读取和写入变量引用的线程安全性

Java 读取和写入变量引用的线程安全性,java,multithreading,Java,Multithreading,我读过,其中(除其他几点外)指出: 读取和写入是引用变量的原子 这意味着,假设我理解正确,下面的代码是线程安全的,不需要volatile,synchronized,也不需要使用锁类,因为otherHashlist到hashlist的实际赋值是原子的,而从hashlist到tempHashlist的赋值也是原子的 public class SomeClass { private HashMap<Integer, ArrayList<MyObject>> hashlist

我读过,其中(除其他几点外)指出:

读取和写入是引用变量的原子

这意味着,假设我理解正确,下面的代码是线程安全的,不需要
volatile
synchronized
,也不需要使用
类,因为
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));
  }
}
公共类SomeClass
{
私有HashMap hashlist=null;
//…周期性调用set()的线程/可运行逻辑。。。
私有空集()
{
HashMap otherHashlist=新建HashMap();
//…填充其他哈希列表。。。
hashlist=otherHashlist;
}
公共阵列列表获取(int i)
{
HashMap tempHashlist=hashlist;
返回新的ArrayList(tempHashlist.get(i));
}
}
此外,除了
get()
set()
之外,永远不会以任何方式访问
hashlist
set()
也不能由类之外的任何对象直接或间接调用。
get()
返回的ArrayList是
new
,因此修改ArrayList(set()、remove()等)的操作不会影响
哈希列表中的原始ArrayList。我还没有在
MyObject
上定义任何setter方法,其所有成员变量要么是
private final int
private final long
,要么是
private final String


那么,我的问题是:这段代码实际上是线程安全的吗?或者我是否有一些假设/我缺少的角度会使这不安全?

取决于预期的行为

如果每个线程都有自己的
SomeClass
实例,那么它是线程安全的。因此,让我们假设多个线程具有相同的实例:

现在假设两个线程同时调用
set
,在其中一个线程执行赋值之后:
hashlist=otherHashlist另一个线程正在做完全相同的事情(可能有不同的内容)。
这意味着对
get(i)
的两个连续调用可能会返回不同的结果,这意味着没有保持一致性。此外,由于线程具有本地缓存,可能有些线程会看到
hashlist
的较旧(过时)副本

这是公认的行为吗?如果是(在我看来这有点奇怪),那么你就很好


我强烈推荐阅读:Brian Goetz,关于“3.5.安全出版”一章的内容取决于预期行为

如果每个线程都有自己的
SomeClass
实例,那么它是线程安全的。因此,让我们假设多个线程具有相同的实例:

现在假设两个线程同时调用
set
,在其中一个线程执行赋值之后:
hashlist=otherHashlist另一个线程正在做完全相同的事情(可能有不同的内容)。
这意味着对
get(i)
的两个连续调用可能会返回不同的结果,这意味着没有保持一致性。此外,由于线程具有本地缓存,可能有些线程会看到
hashlist
的较旧(过时)副本

这是公认的行为吗?如果是(在我看来这有点奇怪),那么你就很好


我强烈建议阅读:Brian Goetz,关于“3.5.安全发布”一章,这不是线程安全的,因为尽管设置字段是安全的,不会导致重复更新,但其他线程可能看不到更改。也就是说,即使
Thread1
hashlist
设置为某个值,
Thread2
也可能看不到该更改。这是因为JVM可以优化对
Thread2
hashlist
的访问,比如将引用复制到寄存器中,这样就不需要多次执行
getfield
。在这种情况下,当
Thread1
更改
hashlist
时,
Thread2
看不到它,因为它不再使用该字段,而是使用它的本地副本,它认为该副本就是该字段。显示在这种情况下可能发生的情况


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

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


最简单的方法是给hashlist添加标签,这意味着一个线程中的更改实际上会被其他线程看到。

无关:为什么需要以下行:
HashMap tempHashlist=hashlist
当您可以直接从
hashlist
获取时?将
hashlist
的所有元素分配给一个新的ArrayList肯定不是原子的,我不确定如果
set()会发生什么
是通过构造新的ArrayList部分调用的。我所指的行与将hashlist的所有元素分配给新的ArrayList完全不同-它是一个引用分配。无关:为什么需要以下行:
HashMap tempHashlist=hashlist当您可以直接从
hashlist