当前位置: 首页 > 知识库问答 >
问题:

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

穆鸿卓
2023-03-14

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

/** 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,但这可能会对性能产生负面影响。显然,正确性更重要,但似乎我应该在将其从性能敏感区域中删除之前证明这是不正确的。

根据java。util。并发文档中,以下创建关系发生在关系之前:

线程中的每个操作都发生在该线程中的每个操作之前,该线程中的每个操作按照程序的顺序稍后出现。

对易失性字段的写入发生在对该字段的每次后续读取之前。易失性字段的写入和读取具有与进入和退出监视器相似的内存一致性效果,但不需要互斥锁定。

所以听起来像是:

  • 矩阵写入发生-发布()之前(规则1)
  • 发布()发生-之前同步更改()(规则2)
  • syncChange()发生在矩阵读取(规则1)之前

所以代码确实为矩阵建立了一个先发生后发生的链。

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

共有3个答案

申高卓
2023-03-14

使用volatile并不是同步所有内容的灵丹妙药。可以保证,如果另一个线程读取一个易失性变量的更新值,他们也会看到在此之前对非易失性变量所做的每一次更改。但没有任何东西可以保证另一个线程将读取更新后的值。

在示例代码中,如果您多次写入矩阵然后调用发布(),其他线程调用synch()然后读取矩阵,那么其他线程可能会看到一些、全部或没有更改:

  • 所有的更改,如果它读取更新后的值从发布()
  • 没有任何更改,如果它读取旧的已发布值并且没有任何更改泄漏通过
  • 一些更改,如果它读取之前发布的值,但一些更改已泄露通过

参见本文

孙辰阳
2023-03-14

这对于向矩阵发布单个更新看起来确实安全,但当然它不提供任何原子性。是否可以取决于您的应用程序,但它可能应该记录在这样的实用程序类中。

但是,它包含一些冗余,可以通过使sync字段最终来改进。该字段的易失性访问是两个内存障碍中的第一个;根据合同,调用增量AndGet()对内存的影响与对易失性变量的写入和读取相同,调用get()与读取具有相同的效果。

因此,代码可以单独依赖这些方法提供的同步,并使字段本身最终

钱钊
2023-03-14

就可见性而言,您只需要在任何易失性字段上进行易失性写-读。这会有用的

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,它是读写锁的一个版本,读锁的价格要便宜得多。但它也很难使用。基本上,读者获得当前版本,然后继续阅读一组变量,然后再次检查版本;如果版本没有更改,则在读取会话期间没有并发写入。

 类似资料:
  • 问题内容: 如果我在代码中使用如下语句 它将数组初始化为特定的东西吗?(例如0)我似乎记得此文件记录在某处,但我不确定要搜索什么。 问题答案: JLS说,在15.10数组创建表达式中 […]创建具有指定长度的一维数组,并将数组的每个组件初始化为其默认值 在4.12.5变量的初始值处表示: 对于type ,默认值为零,即。

  • 我得到一个包含100行数据的表。(Sqlite3和Linux) 这些行上的每个都由多个进程更新。既然同一行不能修改两次(一个进程“拥有”一行并且只有一行),你认为我真的需要使用事务吗?

  • 我想我在并发s3写入方面有问题。两个(或更多)进程同时将几乎相同的内容写入相同的s3位置。我想确定控制这种情况的并发规则。 按照设计,除了一个进程外,所有进程都会在写入s3时被杀死。(我说过,他们写的内容“几乎”相同,因为除了一个进程之外,所有进程都被杀死了。如果所有进程都被允许生存,他们最终会写相同的内容。) 我的理论是,被终止的进程在s3上留下了一个不完整的文件,而另一个文件(可能已完全写入)

  • 我试图写一个通用函数,将写一个未压缩和压缩文件(取决于用户输入)。根据zlib,您只需将gzopen模式设置为“w0”(无压缩),但我仍然得到zlib头! 在ZLIB手册中,它提到可以写入原始数据(没有头/尾),但没有说明如何写入。我如何用zlib编写一个普通的(原始编码的)文件?

  • 问题内容: 我想检测Java数组中的重复值。例如: 如何获得特定的重复条目以及它出现了多少次? 问题答案: 我将拥有一个,其中第一个整数是数组中出现的数字的整数,第二个整数是(出现的数字)。 循环运行 对于数组中的每个项目,请执行。如果地图中存在一个数字,请将该数字递增(类似。否则,请在地图中创建一个新条目(例如。 最后,遍历地图并检索值大于1的所有键。

  • 问题内容: 我一直在尝试创建自己的库,以使用反射来检查方法命名模式和方法返回类型,以从类到xml以及从xml到类实例的原始类型进行序列化和反序列化。 到目前为止,我已经能够使用所有基本的基本类型进行此操作,但是我被困在序列化相同基本类型的数组上。 例如,我调用class方法来获取原语数组: 该方法将仅返回一个原始数组等。尽管我们不知道它将是哪一个。 我试过使用通用的 但这不能让我从原始数组转换为对