Java 如何在刷新期间锁定哈希映射?
我有一个静态的Java 如何在刷新期间锁定哈希映射?,java,concurrency,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) {
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??
}
}
@ThreadSafe
公共类MyService{
私有静态最终映射=新HashMap();
私人密道;
公共无效刷新(列出对象){
map.clear();
map.addAll(dao.findAll());//可能是长时间运行的例程
}
公共对象获取(字符串键){
map.get(key);//确保在刷新期间等待此操作??
}
}
我是否应该引入一个简单的
布尔锁
,在刷新()过程中设置并清除该锁
?还是有更好的选择?或者同步
机制是一种可行的方法吗?在这里,使用同步块或读写锁是更好的选择。这样,您就不必更改调用代码中的任何内容
您也可以使用concurrentHash,但在这种情况下,对于聚合操作(如putAll和clear),并发检索可能只反映插入或删除某些条目。您可以使用volatile映射,并在填充后重新分配它:
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??
}
}
公共类MyService{
私有静态volatile Map=newhashmap();
私人密道;
公共无效刷新(列出对象){
Map newMap=newhashmap();
newMap.addAll(dao.findAll());//可能是长时间运行的例程
map=newMap;
}
公共对象获取(字符串键){
map.get(key);//确保在刷新期间等待此操作??
}
}
它是非阻塞的,从newMap
到map
的分配是原子的,并确保可见性:任何后续调用get
都将基于刷新的映射
就性能而言,这应该可以很好地工作,因为易失性读取几乎与正常读取一样快。Volatile写入速度稍微慢一点,但考虑到刷新频率,这不应该是一个问题。如果性能很重要,您应该运行适当的测试
注意:您必须确保没有外部代码可以访问
map
引用,否则该代码可能会访问过时的数据。对于这样一个全局映射,您需要clear()
然后addAll()
,这很奇怪。我发现您的问题需要通过ReadWriteLock
受保护的双缓冲正确解决
无论如何,从纯性能的角度来看,在CPU内核总数小于32且读写比写多得多的普通服务器上,
ConcurrentHashMap
可能是您的最佳选择。否则需要逐个研究。请不要将map属性设置为静态,所有访问器方法都是非静态的
如果get
应该等待或refresh
改变映射而不是完全交换它,那么ReadWriteLock就是一种方法。ConcurrentMap如果集合发生了变异,但get
不应等待
但是如果refresh
完全取代了map,我可能会建议不同的非等待实现:
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);
}
}
public void refresh(){
Map objs=dao.findAll();
已同步(此){
map.clear();
map.addAll(objs);
}
}
公共对象获取(字符串键){
已同步(此){
返回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;
}
//番石榴的ImmutableHashMap代替Map会更好
private volatile Map=new HashMap();
公共无效刷新(){
Map=dao.findAll();
this.map=map;
}
3) 非更改集合的原子引用
也可以使用原子引用代替易失性引用。可能更好,因为它比容易漏掉的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);
}
//番石榴的ImmutableHashMap代替Map会更好
专用最终原子参考mapRef=
新的原子引用(新的HashMap());
公共无效刷新(){
mapRef.set(dao.findAll());
}
公共对象获取(字符串键){
返回map.get().get(键);
}
我认为布尔锁是一种方法。为什么不使用concurrentHashMap?使用锁是一种简单的方法,而且可以工作。如果你想做更多的设计,我认为信号灯原则将是一个不错的选择。并发hashmap会确保clear+addAll之间不可能有get()
?我不这么认为。这确实是一个很好的解决方案,我们会这样做。这是有效的,因为我们假设只有一个线程在写,对吗?否则,我可能会丢失更新。@atamanroman,因为刷新调用问题代码中的清除,这是预期的行为。@atamanroman无论线程数多少,它都会工作-调用get
的线程将始终看到映射的最新(完整)更新。