[英]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??
}
}
它是非阻塞的,从newMap
到map
的分配是原子的,并确保可见性:任何后续的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.