4

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

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 仅由 reader 线程修改,
writeIdx 仅由 writer 线程修改。

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

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

谢谢弗兰克

4

3 回答 3

5

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

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

于 2015-06-30T19:04:02.797 回答
2

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

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

于 2015-06-30T19:07:44.783 回答
1

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

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

于 2015-06-30T19:11:34.727 回答