【问题标题】:volatile hashmap with intermittent addition of new entries不稳定的哈希图,间歇性地添加新条目
【发布时间】:2018-01-25 16:08:28
【问题描述】:

我有一个用例,我将有一个开始为空的 Hashmap。随着应用程序的运行,缓存将被填满。多个线程将同时访问缓存中的条目。线程访问的条目不会被修改。这些是只读副本。

但要求是,如果任何特定线程在缓存中找不到它正在寻找的对象的副本,它将创建该对象并将其添加到缓存中。一旦该副本可用,就不必再次创建它。

我考虑使用 Volatile Hashmap 的原因是,它们强制执行 happens-before 语义,因此如果地图获得新条目,所有线程都可以看到它。由于线程永远不会修改缓存中的条目,因此我对使用 ConcurrentHashMap 犹豫不决。我的理解正确吗?

【问题讨论】:

  • 在我看来,ConcurrentHashMap 非常适合使用 - 你为什么犹豫不决?
  • 您对volatile 的理解似乎是错误的。 ConcurrentHashMap.computeIfAbsent() 或 Guava 的 Cache 将是一个合适的解决方案。
  • 我对使用并发 hashmap 犹豫不决,因为我不想在读取期间产生额外的锁定记录成本,我认为并发 hashmap 可以做到这一点。我的用例多线程可以并且将从缓存中访问相同的记录,并且它们不会更新记录。他们只会阅读记录

标签: java multithreading caching


【解决方案1】:

不,这不会按您的预期工作。我经常看到这种关于 volatile 的特殊观点,值得研究一下。

成员引用和其中包含 volatile 关键字的类型在两个操作之外不会获得任何与并发相关的特殊属性:

  • 易失性读取(例如访问字段)
  • 易失性写入(例如,分配给字段)

就是这样。并且这些特殊操作 (17.4.2) 仅适用于对成员字段本身的操作,不适用于任何可能从存储对象调用的方法。

例如:

private volatile List<Foo> foos = null;

private void assign() {
    foos = new ArrayList<>(); // This is a volatile write

    // This mutation is not handled any differently than any other list.
    // It is not special simply because the list referenced happens
    // to be assigned to a volatile. If another thread accessed the
    // field 'foos' it may see an inconsistent state (null, empty list,
    // or possible worse) because this is not thread-safe.
    foos.add(new Foo()); 
}

把上面的突变想象成这样:

List<Foo> local = foos; // This is a volatile read, and is specially handled
local.add(new Foo()); // There is no special handling of this mutation

那么,volatile 的意义何在?好吧,如前所述,唯一区别对待的两个操作是分配给 volatile 字段和访问 volatile 字段。易失性写入与其他线程中对该字段的任何读取(访问)创建所谓的“发生前”关系。简而言之,如果线程 A 对字段执行易失性写入,则线程 B 访问该字段,它将看到它的先前状态或新状态。与上面的示例相比,线程 B 可以看到对象处于不一致的状态,而上面的示例中另一个线程可以看到 foos 处于不一致的状态,因此不会存在中间状态。您可以利用它来发挥自己的优势:

private volatile List<Bar> bars = null;

private void assign() {
    List<Bar> local = new ArrayList<>(); // local copy
    local.add(new Bar());
    bars = local; // Volatile write
}

看到这里的不同之处在于我们在本地创建了一个对象,将其完全初始化,然后将其分配给 volatile 成员。现在,访问字段“bars”的任何线程都将看到 null(先前的状态)或带有一个元素的完全构造的列表。这当然只适用于您不尝试就地改变列表,而且重要的是,只要您调用访问器方法时列表不会自行改变。

另外,只需使用 ConcurrentHashMap。

【讨论】:

    猜你喜欢
    • 2021-04-12
    • 1970-01-01
    • 2011-01-07
    • 2012-09-03
    • 2012-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-26
    相关资源
    最近更新 更多