Java 通过手动触发内存屏障避免并发结构 背景

Java 通过手动触发内存屏障避免并发结构 背景,java,multithreading,memory-barriers,Java,Multithreading,Memory Barriers,我有一个类,其实例用于收集和发布数据(使用): 您会注意到,我正在将加载缓存和嵌套并发哈希集的并发级别设置为1(这意味着它们各自只从一个基础表读取和写入)。这是因为我一次只希望有一个线程读取和写入这些对象 (引用javadoc的话,“值1一次只允许一个线程修改映射,但由于读取操作可以并发进行,因此这仍然比完全同步产生更高的并发性。”) 问题 因为我可以假设一次只有一个读写器,所以我觉得对每个对象使用多个并发哈希映射是很费力的。这种结构旨在处理并发读写,并保证并发写的原子性。但是在我的例子中,原子

我有一个类,其实例用于收集和发布数据(使用):

您会注意到,我正在将加载缓存和嵌套并发哈希集的并发级别设置为
1
(这意味着它们各自只从一个基础表读取和写入)。这是因为我一次只希望有一个线程读取和写入这些对象

(引用javadoc的话,“值1一次只允许一个线程修改映射,但由于读取操作可以并发进行,因此这仍然比完全同步产生更高的并发性。”)

问题 因为我可以假设一次只有一个读写器,所以我觉得对每个对象使用多个并发哈希映射是很费力的。这种结构旨在处理并发读写,并保证并发写的原子性。但是在我的例子中,原子性并不重要——我只需要确保每个线程看到最后一个线程的更改

在我寻找更优化的解决方案时,我遇到了这样一个问题,即:

线程之间共享的任何数据都需要“内存屏障”来确保其可见性

[……]

对声明为
volatile
的任何成员的更改对所有人都可见 线程。实际上,写操作是从任何缓存“刷新”到主缓存的 内存,任何访问主内存的线程都可以看到它

现在它变得有点棘手了。在此之前线程进行的任何写入 线程对易失性变量的写入也会被刷新。同样,当 线程读取易失性变量,清除其缓存,然后 后续读取可能会从主内存重新填充它

[……]

实现此功能的一种方法是让正在填充的线程 共享数据结构将结果分配给
volatile
变量。[...] 当其他线程访问该变量时,不仅可以保证 获取该变量的最新值,以及任何更改 在分配值之前由线程对数据结构进行修改 对变量进行修改

(有关内存屏障的进一步说明,请参阅。)

erickson所解决的问题稍有不同,因为所讨论的数据结构是完全填充的,然后分配给一个变量,他建议将该变量设置为
易失性
,而我的结构是分配给
最终
变量,并在多个线程中逐渐填充。但他的回答建议我可以使用
volatile
虚拟变量手动触发内存屏障:

public class ThreadVisibleDataCollector {

    private final SetMultimap<String, String> valueSetsByLabel
            = HashMultimap.create();

    private volatile boolean dummy;

    private void readMainMemory() {
        if (dummy) { }
    }

    private void writeMainMemory() {
        dummy = false;
    }

    public void addLabelValue(String label, String value) {
        readMainMemory();
        valueSetsByLabel.put(label, value);
        writeMainMemory();
    }

    public Set<String> getLabels() {
        readMainMemory();
        return valueSetsByLabel.keySet();
    }

    public Set<String> getLabelValues(String label) {
        readMainMemory();
        return valueSetsByLabel.get(label);
    }
}
公共类ThreadVisibleDataCollector{
私有最终设置MultiMap valueSetsByLabel
=HashMultimap.create();
私有可变布尔伪;
私有void readmain内存(){
if(dummy){}
}
私有void writeMainMemory(){
虚拟=假;
}
public void addLabelValue(字符串标签,字符串值){
readMainMemory();
valueSetsByLabel.put(标签、值);
writeMainMemory();
}
公共集合getLabels(){
readMainMemory();
返回值SetsByLabel.keySet();
}
公共设置GetLabelValue(字符串标签){
readMainMemory();
返回值SetsByLabel.get(标签);
}
}
理论上,我可以更进一步,让调用代码触发内存障碍,以避免在同一线程上的调用之间进行不必要的易失性读写(可能通过使用Java 8中添加的
Unsafe.loadFence
Unsafe.storeFence
)。但这似乎过于极端,难以维持

问题:
我是否从阅读erickson的答案(以及JMM)中得出了正确的结论,并正确地实现了
ThreadVisibleDataCollector
?我找不到使用
volatile
伪变量触发内存屏障的例子,所以我想验证一下,这段代码在整个体系结构中的行为是否符合预期

好吧,这仍然不是特别安全,b/c它在很大程度上取决于HashMultimap的底层实现

您可以查看以下博文进行讨论:

对于这类事情,一种常见的模式是将“最新版本”加载到一个可变变量中,并让您的读者通过该变量读取不可变的版本。这就是
CopyOnWriteArrayList
的实现方式

类似于

class Collector {
   private volatile HashMultimap values = HashMultimap.create();

   public add(String k, String v) {
      HashMultimap t = HashMultimap.create(values);
      t.put(k,v);
      this.values = t;  // this invokes a memory barrier
   }

   public Set<String> get(String k) {
     values.get(k); // this volatile read is memory barrier
   }
}
类收集器{
私有volatile HashMultimap value=HashMultimap.create();
公共添加(字符串k、字符串v){
HashMultimap t=HashMultimap.create(值);
t、 put(k,v);
this.values=t;//这将调用内存屏障
}
公共集get(字符串k){
value.get(k);//此易失性读取是内存屏障
}
}
然而,您和我的解决方案仍然有一点问题——我们都返回底层数据结构的可变视图。我可能会将
HashMultimap
更改为
ImmutableMultimap
,以解决可变性问题。还要注意,调用方保留对完整内部映射(而不仅仅是返回集)的引用,这是视图的副作用


创建一个新副本似乎有点浪费,但我怀疑如果你只有一个线程,那么你就了解了变化的速度,并可以决定这是否合理。例如,如果您希望返回
设置
实例,这些实例会随着情况的变化而动态更新,那么基于map maker的解决方案似乎不会过于繁重。

一些值会写入volatile变量,然后才能从中读取该值。因此,您想要的可见性保证将通过读/写来实现,因此答案是是的,这解决了可见性问题

除了t
public class ThreadVisibleDataCollector {

    private final SetMultimap<String, String> valueSetsByLabel
            = HashMultimap.create();

    private volatile boolean dummy;

    private void readMainMemory() {
        if (dummy) { }
    }

    private void writeMainMemory() {
        dummy = false;
    }

    public void addLabelValue(String label, String value) {
        readMainMemory();
        valueSetsByLabel.put(label, value);
        writeMainMemory();
    }

    public Set<String> getLabels() {
        readMainMemory();
        return valueSetsByLabel.keySet();
    }

    public Set<String> getLabelValues(String label) {
        readMainMemory();
        return valueSetsByLabel.get(label);
    }
}
class Collector {
   private volatile HashMultimap values = HashMultimap.create();

   public add(String k, String v) {
      HashMultimap t = HashMultimap.create(values);
      t.put(k,v);
      this.values = t;  // this invokes a memory barrier
   }

   public Set<String> get(String k) {
     values.get(k); // this volatile read is memory barrier
   }
}
/**
 * Ensures lack of reordering of loads before the fence
 * with loads or stores after the fence.
 */
void loadFence();

/**
 * Ensures lack of reordering of stores before the fence
 * with loads or stores after the fence.
 */
void storeFence();

/**
 * Ensures lack of reordering of loads or stores before the fence
 * with loads or stores after the fence.
 */
void fullFence();