如何在刷新期间锁定哈希图?

How to lock a hashmap during refresh?

我有一个静态 HashMap 在应用程序启动时填充,并且每天刷新。

如何确保在刷新期间没有其他线程可以访问地图?

@ThreadSafe
public class MyService {

   private static final Map<String, Object> map = new HashMap<>();
   private MyDao dao;

   public void refresh(List<Object> objects) {
       map.clear();
       map.addAll(dao.findAll()); //maybe long running routine
   }

   public Object get(String key) {
       map.get(key); //ensure this waits during a refresh??
   }
}

我应该介绍一个在 refresh() 期间设置和清除的简单 boolean lock 吗?或者有更好的选择吗?或者 synchronized 机制是可行的方法吗?

在这里使用同步块或 ReadWriteLock 会是更好的选择。这样,您就不必更改调用代码中的任何内容。

您也可以使用 concurrentHash,但在那种情况下,对于 putAll 和 clear 等聚合操作,并发检索可能反映仅插入或删除某些条目。

您可以使用可变地图并在填充后重新分配它:

public class MyService {

   private static volatile Map<String, Object> map = new HashMap<>();
   private MyDao dao;

   public void refresh(List<Object> objects) {
       Map<String, Object> newMap = new HashMap<>();
       newMap.addAll(dao.findAll()); //maybe long running routine
       map = newMap;
   }

   public Object get(String key) {
       map.get(key); //ensure this waits during a refresh??
   }
}

它是非阻塞的,从 newMapmap 的分配是原子的并确保可见性:对 get 的任何后续调用都将基于刷新的地图。

就性能而言,这应该运行良好,因为易失性读取几乎与正常读取一样快。易失性写入稍微慢一点,但考虑到刷新频率,这应该不是问题。如果性能很重要,您应该 运行 进行适当的测试。

注意:您必须确保没有外部代码可以访问 map 引用,否则该代码可能会访问过时的数据。

对于这样的全球地图,您需要 clear() 然后 addAll() 很奇怪。我闻到你的问题需要通过 ReadWriteLock 受保护的双缓冲来妥善解决。

无论如何,从纯粹的性能角度来看,在CPU核心总数<32,并且读多于写的普通服务器机器上,ConcurrentHashMap可能是你最好的选择。否则需要具体情况具体分析

请不要将映射属性设为静态,所有访问器方法都是非静态的。

如果 get 应该等待或 refresh 改变映射而不是完全交换它,那么 ReadWriteLock 是可行的方法。 ConcurrentMap 如果集合发生变异但 get 不应等待。

但是如果 refresh 完全替换地图,我可能会建议不同的非等待实现:

1) 在同步块外执行长 运行ning 操作

public void refresh() {
       Map<String, Object> objs = dao.findAll();
       synchronized(this) {
         map.clear();
         map.addAll(objs); 
       }
}

public Object get(String key) {
    synchronized(this) {
       return map.get(key); 
    }
}

读者不是运行并行,但完全有效。

2) 使用未更改集合的易失性非最终引用:

// guava's ImmutableHashMap instead of Map would be even better
private volatile Map<String, Object> map = new HashMap<>();

public void refresh() {
    Map<String, Object> map = dao.findAll();
    this.map = map;
}

3) 未更改集合的原子引用

也可以使用 AtomicReference 而不是 volatile 引用。可能更好,因为比容易错过的 volatile 更明确。

// guava's ImmutableHashMap instead of Map would be even better
private final AtomicReference<Map<String, Object>> mapRef = 
    new AtomicReference<>(new HashMap<String, Object>());

public void refresh() {
    mapRef.set(dao.findAll());
}

public Object get(String key) {
    return map.get().get(key); 
}