Java 多线程原子引用赋值

2022-09-02 12:40:21

我有一个缓存,我使用简单的HashMap实现。喜欢 -

HashMap<String,String> cache = new HashMap<String,String>();

此缓存大部分时间都用于从中读取值。我有另一种方法来重新加载缓存,在此方法内部,我基本上创建了一个新的缓存,然后分配引用。据我所知,对象引用的赋值在Java中是原子的。

public class myClass {
     private HashMap<String,String> cache = null;
    public void init() {
       refreshCache();
    }
    // this method can be called occasionally to update the cache.
    public void refreshCache() {
        HashMap<String,String> newcache = new HashMap<String,String>();
       // code to fill up the new cache
       // and then finally
       cache = newcache; //assign the old cache to the new one in Atomic way
    }
}

我知道,如果我不将缓存声明为易失性,其他线程将无法看到更改,但对于我的用例来说,将缓存中的更改传播到其他线程并不是时间紧迫的,他们可以继续长时间使用旧缓存。

是否看到任何线程问题?考虑到许多线程正在从缓存中读取,并且仅在重新加载缓存时。

编辑 - 我的主要困惑是我在这里不必使用原子引用,因为赋值操作本身是原子的?

编辑 - 我明白,为了使排序正确,我应该将缓存标记为易失性。但是,如果 refreshCache 方法被标记为已同步,我不必将缓存设置为易失性,因为同步块将负责排序和可见性?


答案 1

没有适当的内存屏障是不安全的。

有人会认为缓存的分配(缓存= newCache)将在填充缓存的步骤之后发生。但是,其他线程可能会受到这些语句的重新排序的影响,以便在填充缓存之前显示分配。因此,可以在完全构建新缓存之前获取新缓存,或者更糟糕的是看到 ConcurrentModificationException。

您需要强制实施“发生前”关系以防止这种重新排序,并且将缓存声明为易失性将实现这一点。


答案 2

应将缓存标记为 。volatile

虽然您注意到其他线程可能会继续使用陈旧的缓存“很长一段时间”,但您应该注意,如果没有同步边缘,它们可能会永远继续使用陈旧的缓存。这可能不是想要的行为。

按优先顺序(主要是由于可读性):

  • 更新同步方法中的字段
  • AtomicReference<Map<>>
  • volatile

另请参阅此问题