21

我有一个缓存类,其中包含一个volatile HashMap<T>存储缓存项。

很好奇换成会有什么volatile HashMap后果ConcurrentHashMap

我会获得性能提升吗?此缓存是只读缓存。

最好的选择是什么?只是HashMap?缓存是按时间间隔填充的。

4

4 回答 4

46

首先,您似乎不了解volatile关键字的作用。它确保如果声明的变量所持有的引用值volatile发生更改,其他线程将看到它而不是缓存副本。它与访问线程安全无关HashMap

鉴于此,以及您说的是只读的事实HashMap......您当然不需要使用任何提供线程安全的东西,包括ConcurrentHashMap

编辑添加:您的最后一次编辑现在说“缓存正在按时间间隔填充”

那不是只读的,是吗?

如果您在编写(更新现有的 HashMap)时要让线程从中读取,那么您应该使用 a ConcurrentHashMap,是的。

如果您要填充一个全新的HashMap然后将其分配给现有变量,那么您使用volatile

于 2012-04-27T21:09:52.843 回答
6

你说缓存是只读的,但也会在一个看起来矛盾的时间间隔内更新。

如果整个缓存定期更新,我会继续使用 volatile。volatile 将确保更新后的地图安全发布。

public final class  Cache
{
   private volatile Map<?,?> cache;

   private void mapUpdate() {
      Map<?,?> newCache = new HashMap<>();

      // populate the map

      // update the reference with an immutable collection
      cache = Collections.unmodifiableMap(newCache);
   }
}

如果间隔更新正在修改同一个缓存,那么您可能想要使用 ConcurrentHashMap,或者复制映射、更新副本并更新引用。

public final class  Cache
{
   private volatile Map<?,?> cache;

   private void mapUpdate() {
      Map<?,?> newCache = new HashMap<>(cache);

      // update the map

      // update the reference with an immutable collection
      cache = Collections.unmodifiableMap(newCache);
   }
}
于 2012-04-27T21:22:22.690 回答
0

我的 Web 应用程序有一个类似的用例。我正在为我的内存缓存使用 HashMap。用例如下 -

  1. 一个用户请求进来,首先使用输入键检查缓存是否存在记录。这是在 add 方法中完成的。
  2. 如果对象不存在,则它将新记录插入缓存中。
  3. 同样,在 remove 方法中,首先使用键检查缓存中是否存在记录,如果找到则将其删除。

我想确保两个线程同时执行一个添加和另一个删除方法,这种方法会确保他们在他们看到缓存中的最新数据吗?如果我没有错,那么同步方法负责线程安全,而 volatile 负责可见性。

private volatile HashMap<String,String> activeRequests = new HashMap<String,String>();
public synchronized boolean add(String pageKey, String space, String pageName) {
    if (!(activeRequests.get(pageKey) == null)) {
       return false;
    }
    activeRequests.put(pageKey, space + ":" + pageName);
    return true;
}

public synchronized void remove(String pageKey) {       
    if(!(activeRequests.get(pageKey) == null))
        activeRequests.remove(pageKey);
    }
于 2019-08-09T04:35:53.583 回答
0

AFAIK,虽然第一个答案解释正确,但根据用例,在缓存上使用经常刷新和替换的 volatile 是不必要的开销,假设这只是静态元数据快照而不由其他线程更新,实际上可能是坏的或不一致的。

如果你举一个 Http Request 的例子,它从缓存中读取所有内容以获取所需的所有内容,该请求使用映射的引用,然后开始从引用中读取一些键,然后在读取的中途,缓存引用更新为新的哈希图(刷新),现在它开始读取缓存的不同状态,如果缓存中的条目不是特定时间快照T ,则可能会变得不一致。使用 volatile,您在 T1 读取 Key1:Val1,在T2读取 Key2:Val2,而您需要在 T1 读取 Val1、Val2 以获取相同的快照。使用 volatile 您的参考总是会更新,您可以第一次读取 Key1:Val1 和第二次读取 Key1:Val2 在同一请求中提供不同的数据。

如果没有 volatile,请求将使用始终指向引用快照的引用,直到完成处理。如果没有 volatile,您将始终在 T1 读取 Key1:Val1 并在T2读取相同的值 Key2:Val1 。一旦使用此引用的所有请求都完成,旧的取消引用映射将被 GCed。

于 2021-06-18T20:21:33.887 回答