繁体   English   中英

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

[英]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机制尚待解决?

您可以使用易失性地图,并在填充后重新分配它:

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参考,否则代码可以访问陈旧数据。

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

如果get应该等待或refresh而不是完全交换地图,则将它变为变量,那么ReadWriteLock是解决之道。 ConcurrentMap,如果集合发生了突变,但get不应该等待。

但是,如果refresh完全取代了地图,我可能会建议其他不需等待的实现:

1)在同步块外进行长时间运行

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更明确。

// 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); 
}

使用同步块或ReadWriteLock将是一个更好的选择。 这样,您无需更改调用代码中的任何内容。

您也可以使用并发哈希,但是在这种情况下,对于诸如putAll和clear之类的聚合操作,并发检索可能只反映了某些条目的插入或删除。

对于这样的全局映射,您需要先clear()然后addAll() ,这很奇怪。 我闻到您的问题需要通过ReadWriteLock保护的双重缓冲来正确解决。

无论如何,从纯粹的性能角度来看,在CPU内核总数小于32的普通服务器上,并且读取比写入更多, ConcurrentHashMap可能是您的最佳选择。 否则,需要逐案研究。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM