Java 易失性哈希映射的特征

Java 易失性哈希映射的特征,java,multithreading,volatile,Java,Multithreading,Volatile,我试图对变量如何声明为 private volatile HashMap<Object, ArrayList<String>> data; 私有易失性HashMap数据; 将在多线程环境中运行 我的理解是,volatile意味着从主内存获取,而不是从线程缓存获取。这意味着,如果一个变量正在更新,我将不会看到新的值,直到更新完成,我不会阻止,而我看到的是最后更新的值。(顺便说一句,这正是我想要的。) 我的问题是,当我检索ArrayList并在线程A中添加或删除字符串,而

我试图对变量如何声明为

private volatile HashMap<Object, ArrayList<String>> data;
私有易失性HashMap数据;
将在多线程环境中运行

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

我的问题是,当我检索
ArrayList
并在线程A中添加或删除字符串,而线程B正在读取时,volatile关键字究竟会影响什么?
HashMap
仅仅是一个函数,还是它的作用也扩展到了
HashMap
的内容(K和V)?也就是说,当线程B获取当前正在线程A中修改的
ArrayList
时,实际返回的是更新开始之前存在的
ArrayList
的最后一个值


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

此处的volatile关键字仅适用于HashMap,而不适用于其中存储的数据,在本例中为ArrayList

如HashMap文档中所述:

请注意,此实现不同步。如果有多个线程 同时访问哈希映射,并至少访问其中一个线程 如果从结构上修改贴图,则必须从外部对其进行同步。(一) 结构修改是添加或删除一个或多个结构的任何操作 更多映射;只需更改与 实例已包含的不是结构修改。)这是 通常通过在某些对象上进行同步来完成 封装地图。如果不存在这样的对象,则应删除地图 使用Collections.synchronizedMap方法“包装”。这是最好的 在创建时完成,以防止意外不同步地访问 地图:

这意味着,如果一个变量正在更新,我将不会看到新的值,直到更新完成,我不会阻止,而我看到的是最后更新的值

这是你困惑的根源。volatile所做的是确保对该字段的读写是原子的,这样其他线程就不会看到部分写入的值

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

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

此外,由于对引用的读/写操作始终是原子的,即使字段未标记为volatile,volatile和非volatile HashMap在原子性方面没有区别。在这种情况下,volatile所做的就是为您提供acquire发布语义。这意味着,即使处理器和编译器仍然允许对指令进行轻微的重新排序,也不能将任何指令移动到易失性读操作之上或易失性写操作之下。

易失性关键字既不会影响HashMap上的操作(例如put、get),也不会影响HashMap中ArrayList上的操作。volatile关键字只影响对HashMap的特定引用的读写。同样,也可以进一步引用相同的HashMap,这不受影响

如果要同步上的所有操作,请执行以下操作: -参考文献 -哈希映射 -还有ArrayList, 然后使用附加的锁定对象进行同步,如以下代码所示

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");
}
private final Object lock=new Object();
私有映射映射=新的HashMap();
//访问参考
已同步(锁定){
map=新的HashMap();
}
//访问参考和HashMap
已同步(锁定){
返回地图。包含(42);
}
//access引用、HashMap和ArrayList
已同步(锁定){
map.get(42).添加(“foobar”);
}

如果引用未更改,则可以使用HashMap进行同步(而不是锁定)。

请注意,
volatile
与引用有关,而不是与被引用的
对象有关。volatile的作用不仅仅是原子读写。在Java中,对引用的读写始终是原子的。但是,如果不使用volatile,执行可能看起来是非顺序一致的。@nosid是的,它引入了半围栏,以在一定程度上防止内存重新排序。我的印象是,问题更多的是原子性效应。关于这一点,我很快会补充一点。我只是指原子性,因为你的全部答案都是关于原子性,而不是关于重新排序。JLS明确指出(17.7):对引用的写入和读取始终是原子的,无论它们是作为32位值还是64位值实现的。@nosid添加了一段关于这一点的内容。
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");
}