【问题标题】:Is it possible to efficiently implement a seqlock in Java?是否可以在 Java 中有效地实现 seqlock?
【发布时间】:2013-01-22 11:05:29
【问题描述】:

另一个question 让我想知道seqlock 是否可以用Java 中的可变版本计数器有效地实现。

这是一个原型实现,在这种情况下,永远只有一个编写器线程:

class Seqlock {
  private volatile long version = 0;
  private final byte[] data = new byte[10];

  void write(byte[] newData) {
    version++;  // 1
    System.arraycopy(newData, 0, data, 0, data.length);  // 2
    version++;  // 3
  }

  byte[] read() {
    long v1, v2;
    byte[] ret = new byte[data.length];
    do {
      v1 = version; // 4
      System.arraycopy(data, 0, ret, 0, data.length);  // 5
      v2 = version; // 6
    } while (v1 != v2 || (v1 & 1) == 1);
  }
}

基本思想是在写入前后增加版本号,并让读者通过验证版本号是否相同甚至偶数来检查他们是否获得了“一致”读取,因为奇数表示“正在写入” .

由于版本是可变的,因此写入线程和读取线程中的关键操作之间存在各种发生前的关系。

但是,我看不到是什么阻止了 (2) 处的写入高于 (1),从而导致读者看到正在进行的写入。

例如,以下 volatile 读取和写入的同步顺序,使用每行旁边的 cmets 中的标签(还显示了 data 读取和写入,它们不是 volatile 因此不属于同步顺序的一部分,缩进):

1a (version is now 1)
  2a (not part of the synchronization order)
3 (version is now 2)
4 (read version == 2, happens before 3)
  5 (not part of the synchronization order)
6 (read version == 2, happens before 4 and hence 3)
1b (second write, version is now 3)
  2b (not part of the synchronization order)

ISTM认为5(数据的读取)和2b(数据的第二次写入)之间没有happens-before,因此2b有可能发生在读取和读取错误数据之前。

如果这是真的,将write() 声明为synchronized 有帮助吗?

【问题讨论】:

  • 我可以很容易地回答最后一个问题:如果读取不同步,则声明写入同步是没有意义的。至于 volatile 是否独立工作,我还在思考。
  • 可能不是您要找的,但您可以在 java 中非常简单地解决这个问题(具有相同的保证),使用对 @987654328 中分配的 byte[] 的简单易失性引用@方法。
  • 我无法理解您的“同步顺序”。你那里有多少个线程?在第四行中,4 (read version == 2, happens before 3) 到底是什么意思?首先发生了什么?我建议你画一个“表格”,每一列都是一个线程,每一行都是“瞬间”。
  • 另外,你有没有用一个简单的 synchronized {} 块将它的性能与类似的类进行比较? JVM 非常非常擅长处理同步块,因为如果没有获得锁(与 volatile 读/写对相比),它对性能几乎没有影响,如果获得了,那么它会愉快地旋转线程在实际放弃和停止线程之前一点点(即,它将循环,类似于您的代码正在执行的操作,但没有无意义地调用 System.arraycopy 并丢弃结果的额外工作)
  • 假设您有多个阅读器,同步块的性能可能会受到影响。在这种情况下,请将您的代码与使用 ReentrantReadWriteLock 而不是同步的锁定版本进行比较。

标签: java optimization concurrency mutex java-memory-model


【解决方案1】:

在java中你可以很简单地实现一个共享缓冲区(或其他对象):

public class SharedBuffer {

  private volatile byte[] _buf;

  public void write(byte[] buf) {
    _buf = buf;
  }

  public byte[] read() {
    // maybe copy here if you are worried about passing out the internal reference
    return _buf;
  }
}

显然,这不是“seqlock”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-09
    • 1970-01-01
    相关资源
    最近更新 更多