如何在刷新期间锁定哈希图?
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??
}
}
它是非阻塞的,从 newMap
到 map
的分配是原子的并确保可见性:对 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);
}
我有一个静态 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??
}
}
它是非阻塞的,从 newMap
到 map
的分配是原子的并确保可见性:对 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);
}