【问题标题】:Does a synchronized block trigger a full memory fence for arrays?同步块是否会触发数组的完整内存围栏?
【发布时间】:2015-12-31 06:58:46
【问题描述】:

我对在 Java 中的线程之间安全共享数组感到困惑,特别是内存栅栏和关键字 synchronized

此问答很有帮助,但并未回答我的所有问题:Java arrays: synchronized + Atomic*, or synchronized suffices?

以下是演示该问题的示例代码。假设有一个工作线程池通过方法add(...) 填充SharedTable。在所有工作线程完成后,最后一个线程读取并保存数据。

演示问题的示例代码:

public final class SharedTable {

    // Column-oriented data entries
    private final String[] data1Arr;
    private final int[] data2Arr;
    private final long[] data3Arr;
    private final AtomicInteger nextIndex;

    public SharedTable(int size) {
        this.data1Arr = new String[size];
        this.data2Arr = new int[size];
        this.data3Arr = new long[size];
        this.nextIndex = new AtomicInteger(0);
    }

    // Thread-safe: Called by worker threads
    public void addEntry(String data1, int data2, long data3) {
        final int index = nextIndex.getAndIncrement();
        data1Arr[index] = data1;
        data2Arr[index] = data2;
        data3Arr[index] = data3;
    }

    // Not thread-safe: Called by clean-up/joiner/collator thread...
    // after worker threads are complete
    public void save() {
        // Does this induce a full memory fence to ensure thread-safe reading of 
        synchronized (this) {
            final int usedSide = nextIndex.get();
            for (int i = 0; i < usedSide; ++i) {
                final String data1 = data1Arr[i];
                final int    data2 = data2Arr[i];
                final long   data3 = data3Arr[i];
                // TODO: Save data here
            }
        }
    }
}

上面的示例代码也可以使用Atomic*Array 来实现,它充当“可变值/引用数组”。

public final class SharedTable2 {

    // Column-oriented data entries
    private final AtomicReferenceArray<String> data1Arr;
    private final AtomicIntegerArray  data2Arr;
    private final AtomicLongArray data3Arr;
    private final AtomicInteger nextIndex;

    public SharedTable2(int size) { ... }

    // Thread-safe: Called by worker threads
    public void addEntry(String data1, int data2, long data3) {
        final int index = nextIndex.getAndIncrement();
        data1Arr.set(index, data1);
        ...
    }

    // Not thread-safe: Called by clean-up/joiner/collator thread...
    // after worker threads are complete
    public void save() {
        final int usedSide = nextIndex.get();
        for (int i = 0; i < usedSide; ++i) {
            final String data1 = data1Arr.get(i);
            final int    data2 = data2Arr.get(i);
            final long   data3 = data3Arr.get(i);
            // TODO: Save data here
        }
    }
}
  1. SharedTable 线程安全(并且缓存一致)吗?
  2. SharedTable(很多?)效率更高,因为只需要一个内存栅栏,而SharedTable2 每次调用Atomic*Array.set(...) 都会调用一个内存栅栏?

如果有帮助,我将在 64 位 x86 硬件(Windows 和 Linux)上使用 Java 8。

【问题讨论】:

  • SharedTableSharedTable2 中方法public void addEntry(..) 上方的注释说此方法是线程安全的。我假设您在调用 public void addEntry(..) 方法时可能正在同步工作线程中的 SharedTableSharedTable2 对象。不是吗?

标签: java arrays multithreading volatile memory-fences


【解决方案1】:

不,SharedTable 不是线程安全的。仅当您从同步块中读取已使用相同锁从同步块中写入的内容时,才会保证发生前发生。

由于写入是由同步块进行的,因此 JMM 不保证读取器线程可以看到写入。

【讨论】:

  • 内容丰富!后续问题:如果我可以调用完整的内存围栏(通过sun.misc.Unsafe),而不是使用synchronized 块,SharedTable 会是线程安全的吗?
  • 我不会使用 Unsafe。我会使用一个原子数组,它可以避免使用不安全的、不应该使用的类,并在代码中明确意图。那应该足够快。如果不是,并且您已经证明问题出在该部分,则开始优化。
  • 我会回到这个答案,因为如果您对 Java 内存模型中的 happens-before 的概念不是很熟悉,它会很有帮助,但也很密集[JMM]。此链接有助于了解更多信息:docs.oracle.com/javase/8/docs/api/java/util/concurrent/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多