易失性HashMap与ConcurrentHashMap

时间:2012-04-27 21:04:41

标签: java caching concurrency hashmap

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

我很好奇将volatile HashMap更改为ConcurrentHashMap会产生什么后果?

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

最好的选择是什么?只是HashMap?缓存正在一段时间内填充。

4 个答案:

答案 0 :(得分:32)

首先,您似乎无法理解volatile关键字的作用。它确保如果由声明为volatile的变量保存的引用值发生更改,则其他线程将看到它而不是具有缓存副本。在访问HashMap

方面,它与线程安全无关

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

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

那不是只读的,不是吗?

如果您正在编写,而正在编写(更新现有的HashMap),那么您应该使用ConcurrentHashMap,是的。

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

答案 1 :(得分: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);
   }
}

答案 2 :(得分:0)

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

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

我要确保两个线程同时在add上执行一个线程,而另一个在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);
    }

答案 3 :(得分:0)

AFAIK,虽然 first answer explains correctly,根据用例,在经常刷新和替换的缓存上使用 volatile 是不必要的开销,并且实际上可能是坏的或不一致的,假设这是只是静态元数据快照,不会被其他线程更新。

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

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