我有一个静态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
机制是一种方法吗?
答案 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??
}
}
它是非阻止的,从newMap
到map
的分配是原子的,可确保可见性:对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
可能是你最好的选择。否则需要逐案研究。