如何在刷新期间锁定hashmap?

时间:2015-01-26 09:58:09

标签: java concurrency

我有一个静态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??
   }
}

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

4 个答案:

答案 0 :(得分:3)

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

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引用,否则该代码可以访问过时数据。

答案 1 :(得分:1)

请不要将map-attribute设为static,所有accessor-methods都是非静态的。

如果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); 
}

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

您需要clear()然后addAll()来获取此类全局地图,这很奇怪。我闻到你的问题需要通过ReadWriteLock受保护的双重缓冲来正确解决。

无论如何,从纯粹的性能角度来看,在具有CPU核心总数的普通服务器盒上&lt; 32,读取比写入更多,ConcurrentHashMap可能是你最好的选择。否则需要逐案研究。