同步不经常更新的hashmap的最佳方法

时间:2014-03-21 14:56:02

标签: java multithreading data-structures synchronization hashmap

我有一个我们在应用程序中使用的HashMap。在应用程序初始加载期间从数据库填充数据,然后它始终只是读取并且从不更新。将有多个线程不断读取数据。由于数据永远不会更新,我们目前不使用任何同步,只使用HashMap。我们现在定义的方式是:

private volatile Map<Integer, MyData> myMap = new HashMap<>();

现在,我们希望每天通过从数据库重新填充来更新地图中的数据。我计划做的是将数据从数据库获取到当地地图,myLocalMap每天午夜说。将数据从数据库加载到myLocalMap后,我只需将myMap交换为指向此内容。

所以我担心的是,在我myMap = myLocalMap这一点上,是否有可能从myMap读取数据的其他线程得到空的或意外的结果? 如果是,我将必须同步myMap。为了同步,我有以下选项:

synchronized(myMap) {} OR // synchronize all map get and update operations
ConcurrentHashMap OR
Collections.synchronizedMap(myMap)

但是我对使用同步犹豫不决,因为我也在同步所有的读取。我认为每天一次刷新同步地图将影响全天不断发生的地图读取。这是特别糟糕的,因为我的应用程序中有许多地图以这种方式读取和更新。有什么想法/评论?谢谢!

4 个答案:

答案 0 :(得分:4)

  

在我做myMap = myLocalMap的时候,是否有可能   从myMap读取数据的其他一些线程得到一个空或   出人意料的结果?

不,没有。读取和写入are atomic作为参考变量,这意味着整个操作一次全部发生,结果对其他线程不可见,直到整个操作完成。因此,任何从'myMap&#39;将得到旧的myMap或新的myMap,但永远不会得到空的或不一致的结果。此外,在&#39; myMap&#39;上使用volatile关键字这意味着所有线程将始终知道新数据:如果myMap已更新,则在更新操作开始后启动的任何读取操作将使用该更新值。

来自Oracle Java教程的支持文档:

  
      
  • 读取和写入对于引用变量和大多数原始变量(除long和double之外的所有类型)都是原子的。
  •   
  • 对volatile变量的任何写入都会建立与之后读取相同变量
  • 的先发生关系   

Vogella

  

如果使用volatile关键字声明变量,那么它就是   保证读取该字段的任何线程都会看到最多   最近的书面价值。

同样来自Vogella的同一篇文章:

  

Java语言规范保证读取或写入   变量是原子操作

另见this引用,特别是&#34;清单3.使用volatile变量进行安全的一次性发布&#34;它描述了一个与你非常相似的场景。

顺便说一句,我同意Giovanni对ConcurrentHashMap的看法。但在您的情况下,您不需要使用ConcurrentHashMap,因为所有更新都发生在单个事务中,而您只是将Map调整为指向新数据。

答案 1 :(得分:3)

使用ConcurrentHashMap。来自javadoc

  

[...]检索操作不需要锁定[...]

编辑:根据评论,由于您的地图实际上是不可变的,因此您也可以使用Guava's ImmutableMap

private volatile Map<K,V> map = ImmutableMap.of();

上面创建了一个空的不可变地图,只读线程安全(客户无法意外修改它,风险为{{1} } S)。

当您需要从数据库重新填充地图时,您只需构建一个新地图:

ConcurrentModificationException

正如@KyleM所指出的那样,将地图声明为volatile意味着上述作业将为atomic,即一旦完成,所有客户都将看到新地图。因此,这应该通过两个简单的步骤解决所有并发问题。

请注意,此模式有点类似于将不可变集合存储在Scala中的ImmutableMap.Builder<K,V> builder = ImmutableMap.builder(); // add your k,v pairs to the builder builder.put(foo,bar); // now swap the map with the new one: map = builder.build(); 中。

作为旁注,您应该查看Guava's caches:它们可能会为您解决更多问题。

答案 2 :(得分:0)

我同意Giovanni,除非你想在更新哈希时锁定读取?然后,您可能需要进行一些自定义同步。

我猜你不希望在更新中发生读取,但我不确定。

答案 3 :(得分:0)

如果您只是使用已初始化的地图和volatile关键字交换属性myMap,则不需要任何类型的同步

如果您需要定期更新某些值,ConcurrentHashMap方法会很好。