【发布时间】: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