原始数组写入的 Java 并发可见性

标签 java concurrency memory-barriers java-memory-model

我最近在我的代码库中发现了这个 gem:

/** This class is used to "publish" changes to a non-volatile variable.
 *
 * Access to non-volatile and volatile variables cannot be reordered,
 * so if you make changes to a non-volatile variable before calling publish,
 * they are guaranteed to be visible to a thread which calls syncChanges
 *
 */
private static class Publisher {
    //This variable may not look like it's doing anything, but it really is.
    //See the documentaion for this class.
    private volatile AtomicInteger sync = new AtomicInteger(0);

    void publish() {
        sync.incrementAndGet();
    }

    /**
     *
     * @return the return value of this function has no meaning.
     * You should not make *any* assumptions about it.
     */
    int syncChanges() {
        return sync.get();
    }
}

这是这样使用的:

线程 1

float[][] matrix;
matrix[x][y] = n;
publisher.publish();

线程 2

publisher.syncChanges();
myVar = matrix[x][y];

线程 1 是一个连续运行的后台更新线程。线程 2 是一个 HTTP 工作线程,它不关心它读取的内容在任何方面都是一致的或原子的,只关心写入“最终”到达那里并且不会作为并发之神的产品而丢失。

现在,这触发了我所有的警钟。深入不相关代码编写的自定义并发算法。

不幸的是,修复代码并非易事。 Java 对并发原始矩阵的支持不好。看起来解决此问题的最明确方法是使用 ReadWriteLock,但这可能会对性能产生负面影响。显然,正确性更为重要,但似乎我应该证明这是正确的,然后才将其从性能敏感区域中剥离出来。

根据 the java.util.concurrent documentation , 以下创建 happens-before 关系:

Each action in a thread happens-before every action in that thread that comes later in the program's order.

A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.

听起来像:

  • 矩阵写入发生在 publish() 之前(规则 1)
  • publish() 发生在 syncChanges() 之前(规则 2)
  • syncChanges() 发生在矩阵读取之前(规则 1)

所以代码确实为矩阵建立了一个 happens-before 链。

但我不相信。并发很难,而且我不是领域专家。 我错过了什么?这样真的安全吗?

最佳答案

就可见性而言,您所需要的只是在任何 volatile 字段上的 volatile 读写。这行得通

final    float[][] matrix  = ...;
volatile float[][] matrixV = matrix;

线程 1

matrix[x][y] = n;
matrixV = matrix; // volatile write

线程 2

float[][] m = matrixV;  // volatile read
myVar = m[x][y];

or simply
myVar = matrixV[x][y];

但这只适用于更新一个变量。如果写入线程正在写入多个变量而读取线程正在读取它们,则读取器可能会看到不一致的画面。通常由读写锁来处理。写时复制可能适合某些使用模式。

Doug Lea 有一个新的“StampedLock”http://gee.cs.oswego.edu/dl/jsr166/dist/jsr166edocs/jsr166e/StampedLock.html对于 Java8,这是一个读写锁的版本,它比读锁便宜得多。但它也更难使用。基本上读者得到当前版本,然后继续读取一堆变量,然后再次检查版本;如果版本未更改,则在读取 session 期间没有并发写入。

关于原始数组写入的 Java 并发可见性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14714847/

相关文章:

java - 如何在 selenium webdriver 中选择单选按钮?

c# - 如何重构这些锁?

c++ - C++0x 中的栅栏,一般只保证原子或内存

java - 如果一个算法调用另一个算法来执行其功能,它的总体时间复杂度是否会受到影响?

java - 如何使用 Java 代码在框架布局底部添加 TextView

java - 除非使用 Thread.interrupted(),否则会错过中断吗?

java - 为什么java并发测试失败?

c - 生产者消费者C编程中的死锁

c++ - C++11 中的内存排序与主内存刷新排序有关吗?

c++ - std::memory_order_relaxed 和初始化