我正在努力确定变量如何声明为
private volatile HashMap<Object, ArrayList<String>> data;
会在多线程环境中运行。
我的理解是volatile
表示从主内存而不是从线程缓存中获取。这意味着如果正在更新变量,我将不会看到新值,直到更新完成并且我不会阻止,而是我看到的是最后更新的值。 (这是完全我想要的BTW。)
我的问题是当我检索ArrayList<String>
并在线程A读取时在线程A中添加或删除字符串时,volatile
关键字究竟会受到什么影响?仅HashMap
或者效果是否也扩展到HashMap
的内容(K和V)?也就是说,当线程B获得当前正在线程A中修改的ArrayList<String>
时,实际返回的是在更新开始之前存在的ArrayList<String>
的最后一个值。
为了清楚起见,我们假设更新正在添加2个字符串。当线程B获得数组时,线程A中已添加一个字符串。线程B是否在添加第一个字符串之前获取数组?
答案 0 :(得分:5)
这意味着如果一个变量正在更新,我将不会看到新值,直到更新完成并且我不会阻止,而是我看到的是最后更新的值
这是你的困惑之源。 volatile的作用是确保对该字段的读取和写入是原子的 - 因此没有其他线程可以看到部分写入的值。
如果写入操作在写入第一个地址之后和写入第二个地址之前被抢占,则非原子长字段(在32位机器上占用2个存储器地址)可能会被错误读取。
请注意,对字段的读/写的原子性与更新HashMap
的内部状态无关。更新HashMap
的内部状态需要多个指令,这些指令不是原子的整体。这就是为什么你使用锁来同步HashMap
的访问权限。
此外,由于对引用的读/写操作始终原子,即使该字段未标记为volatile,因此volatile和非易失性HashMap之间没有区别,关于原子性。在这种情况下,所有volatile都会为您提供获取释放语义。这意味着,即使仍允许处理器和编译器对指令进行轻微重新排序,也不会将指令移到易失性读取之上或低于易失性读取。
答案 1 :(得分:2)
此处的volatile关键字仅适用于HashMap,而不适用于存储在其中的数据,在本例中为ArrayList。
如HashMap文档中所述:
请注意,此实现未同步。如果有多个线程 同时访问哈希映射,以及至少一个线程 从结构上修改地图,必须在外部进行同步。 (一个 结构修改是添加或删除一个或多个的任何操作 更多映射;只是改变与键相关的值 实例已经包含的不是结构修改。)这是 通常通过自然地同步某个对象来完成 封装地图。如果不存在这样的对象,则应该是地图 使用Collections.synchronizedMap方法“包装”。这是最好的 在创建时完成,以防止意外的不同步访问 地图:
Map m = Collections.synchronizedMap(new HashMap(...));
答案 2 :(得分:2)
volatile 关键字既不会影响 HashMap 上的操作(例如 put , get ),也不影响操作 HashMap 中的 ArrayLists 。 volatile 关键字仅影响读取和写入对此 HashMap 的特定引用。同样,可以进一步引用相同的 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 )。