【问题标题】:Characteristics of a volatile hashmap易失性哈希图的特征
【发布时间】:2014-04-08 10:12:12
【问题描述】:

我正在努力弄清楚如何将变量声明为

private volatile HashMap<Object, ArrayList<String>> data;

将在多线程环境中运行。

我的理解是volatile 表示从主内存而不是从线程缓存中获取。这意味着如果正在更新一个变量,在更新完成之前我不会看到新值并且我不会阻塞,而是我看到的是最后更新的值。 (这正是我想要的顺便说一句。)

我的问题是当我检索ArrayList&lt;String&gt; 并在线程B 正在读取时在线程A 中添加或删除字符串时,volatile 关键字究竟会影响什么?仅HashMap 还是将效果扩展到HashMap 的内容(K 和V)?即当线程 B 得到一个当前正在线程 A 中修改的 ArrayList&lt;String&gt; 时,实际返回的是在更新开始之前存在的 ArrayList&lt;String&gt; 的最后一个值。

为了清楚起见,假设更新添加了 2 个字符串。线程 B 获取数组时,线程 A 中已经添加了一个字符串。线程 B 是否按照添加第一个字符串之前的状态获取数组?

【问题讨论】:

  • 请注意,volatile 与引用有关,而不是与被引用的 Object 有关。

标签: java multithreading volatile


【解决方案1】:

这意味着如果正在更新变量,在更新完成之前我不会看到新值并且我不会阻塞,而是我看到的是最后更新的值

这是你的困惑之源。 volatile 所做的是确保对该字段的读取和写入是原子的 - 因此没有其他线程可以看到部分写入的值。

如果写入操作在写入第一个地址之后和写入第二个地址之前被抢占,则非原子长字段(在 32 位机器上占用 2 个内存地址)可能会被错误地读取。

请注意,读取/写入字段的原子性与更新HashMap 的内部状态无关。更新HashMap 的内部状态需要多条指令,这些指令作为一个整体不是原子的。这就是为什么您要使用锁来同步对HashMap 的访问。

另外,由于对引用的读/写操作总是原子的,即使该字段没有标记为 volatile,也没有 volatile 和 non-volatile HashMap 的区别,关于原子性。在这种情况下,所有 volatile 所做的就是为您提供获取-释放语义。这意味着,即使仍然允许处理器和编译器稍微重新排序您的指令,也不能将任何指令移动到 volatile 读取之上或 volatile 写入之下。

【讨论】:

  • volatile 不仅仅是原子读写。在 Java 中,对引用的读取和写入始终是原子的。但是,如果不使用 volatile,则执行可能看起来是非顺序一致的。
  • @nosid 是的,它在一定程度上引入了半栅栏,以防止内存重新排序。我的印象是这个问题更多的是关于原子性效应。我会尽快添加关于此的注释。
  • 我只指 atomicity,因为您的整个答案是关于 atomicity,而不是关于 reordering。 JLS 明确指出 (17.7):对引用的写入和读取始终是原子的,无论它们是作为 32 位还是 64 位值实现的。
  • @nosid 添加了一段关于此的内容。
【解决方案2】:

这里的volatile关键字只适用于HashMap,而不适用于其中存储的数据,本例为ArrayList。

如 HashMap 文档中所述:

注意这个实现是不同步的。如果多线程 同时访问哈希映射,并且至少访问其中一个线程 从结构上修改地图,它必须在外部同步。 (一种 结构修改是添加或删除一个或 更多映射;仅仅改变与一个键关联的值 实例已经包含不是结构修改。)这是 通常通过同步一些自然而然的对象来完成 封装地图。如果不存在这样的对象,则地图应该是 使用 Collections.synchronizedMap 方法“包装”。这是最好的 在创建时完成,以防止意外的不同步访问 地图:

 Map m = Collections.synchronizedMap(new HashMap(...));

【讨论】:

    【解决方案3】:

    volatile 关键字既不影响对 HashMap 的操作(例如 putget)也不影响对HashMap 中的 ArrayListsvolatile 关键字只影响 readswritesHashMap 的特定引用。同样,可以进一步引用相同的 HashMap,它们不受影响。

    如果您想同步所有操作: - 参考资料 - HashMap - 和 ArrayList, 然后使用额外的 Lock 对象进行同步,如下面的代码所示。

    private final Object lock = new Object();
    private Map<Object, List<String>> map = new HashMap<>();
    
    // access reference
    synchronized (lock) {
        map = new HashMap<>();
    }
    
    // access reference and HashMap
    synchronized (lock) {
        return map.contains(42);
    }
    
    // access reference, HashMap and ArrayList
    synchronized (lock) {
        map.get(42).add("foobar");
    }
    

    如果引用没有改变,可以使用HashMap进行同步(代替Lock)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-09-03
      • 2020-08-06
      • 2018-01-18
      • 2018-09-25
      • 1970-01-01
      • 2015-04-16
      • 2012-12-27
      相关资源
      最近更新 更多