【问题标题】:Is volatile needed?需要挥发物吗?
【发布时间】:2015-06-30 18:55:34
【问题描述】:

如果我有一个字节队列,预计会有一个线程生产者,另一个消费者:

class ByteQueue{
    byte[] buf;
    /*volatile?*/ int readIdx;
    /*volatile?*/ int writeIdx;
    Runnable writeListener;
    Runnable readListener;
    // ...
    void write( byte[] b ){
        int wr = writeIdx;
        int rd = readIdx;
        // check consistency and free space using wr+rd
        // copy to buf, starting at wr, eventually wrap around
        // update writeIdx afterwards
        writeIdx = ( wr + b.length ) % buf.length;
        // callback to notify consumer for data available
        writeListener.run();
    }
    void read( byte[] b ){
        int wr = writeIdx;
        int rd = readIdx;
        // check consistency and available data using wr+rd
        // copy buf to b, starting at rd, eventually wrap around
        // update readIdx afterwards
        readIdx = ( rd + b.length ) % buf.length;
        // callback to notify producer for free space available
        readListener.run();
    }
    int available() { return (writeIdx - readIdx) % buf.length; }
    int free() { return buf.length - available() -1; }
    // ...
}

这种类型的队列不需要同步。
readIdx 仅由阅读器线程修改,
writeIdx 仅由编写器线程编写。

readIdx == writeIdx 表示,没有内容。
并且队列最多只能占用 buf.length-1 字节的数据。

是否需要 volatile 或者是否可以省略它们,因为只有一个线程是一个整数状态的修饰符?

谢谢 弗兰克

【问题讨论】:

  • 您需要更强大的同步,writeIdx 更新必须始终发生在buf 更新之后。没有synchronized 会很棘手。
  • 同意班塔尔。谨慎行事,不要走捷径。写入正确的同步。不要为了获得一纳秒而牺牲正确性。
  • 您对修改的看法是对的,但不要忘记读取也在发生。
  • 当任何其他线程正在使用read()write() 方法时,是否可以调用available()?如果是这样,那它应该是什么意思? (提示:你能做的最好的就是返回在某个时刻可用的字节数,但这不一定与是 在调用者决定做某事时可用。)

标签: java multithreading volatile memory-visibility


【解决方案1】:

如果另一个线程必须读取它,它需要是易失的。 volatile 关键字向 JVM 指示该值不能被缓存或对其更新重新排序,否则对其值的更新可能对其他线程不可见。

可见性问题也延伸到 buf 数组。由于 buf 需要与索引同步更改,因此似乎需要同步写入和读取方法。同步使更改可见,并确保并发调用不会导致索引和 buf 内容不一致。

【讨论】:

  • 您也可以使用volatile 来处理缓冲区的内容。诀窍是要记住,读写volatile 字段会在线程之间建立happens-before 关系,因此如果线程A 在更新易失性字段之前将字节写入缓冲区,然后线程B 检查缓冲区after 检查相同的 volatile 字段,然后线程 B 应该看到线程 A 写入缓冲区的字节。但你是对的,synchronized 是更明显的方式...
  • @james:同意,如果 OP 想走这条路,可以让捎带在可见角上工作。
  • 感谢您的帮助。所以我会让它们变得不稳定。不需要同步,因为 writeIdx 是最后更新的,所以读取不能在之前发生,反之亦然。
  • @keinfarbton:是的,这就是 James 在他的评论中所描述的,您可以依靠 volatile 变量创建的发生前关系来确保您看到缓冲区的更新。
【解决方案2】:

您应该声明它们volatile。 例如,让我们看一下readIdx。如果不是volatile,编写线程优化可以假设它永远不会改变,并基于该假设进行错误的优化。

但是,除了分配给某些局部变量rd(或wr)之外,我没有看到您在写入线程(或读取线程中的writeIdx)的任何地方访问readIdx。我只是假设缺少一些代码,否则您的问题实际上没有意义。

【讨论】:

    【解决方案3】:

    Nathan 是正确的,这并不是两个线程会覆盖彼此的变量,而是变量本身对于另一个线程(或者更确切地说是 CPU 核心)可能永远可见

    有一个有趣的队列,它实际上使用非易失性变量来让 CPU 更好地调度工作,LMAX disruptor

    【讨论】:

      猜你喜欢
      • 2021-12-27
      • 2011-01-29
      • 2016-07-01
      • 2018-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      相关资源
      最近更新 更多