jvm重排序/可见性效果测试
在写一些java文章时,我试图重现 重新排序以防多线程环境中对象构造不同步。 当一个重对象被构造而没有同步/易失性/最终并且其他线程在构造函数调用后立即访问它时的情况。 这是我尝试的代码:
public class ReorderingTest {
static SomeObject<JPanel>[] sharedArray = new SomeObject[100];
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
String name = "watcher" + i;
new Thread(new Watcher(name)).start();
System.out.printf("watcher %s started!%n", name);
}
}
static class Watcher implements Runnable {
private String name;
Watcher(String name) {
this.name = name;
}
public void run() {
while (true) {
int randomIndex = (int) (Math.random() * sharedArray.length);
SomeObject<JPanel> item = sharedArray[randomIndex];
if (item == null) {
//System.out.printf("sharedArray[%s]=null%n", randomIndex);
double r = 1 + Math.random() * 1000;
sharedArray[randomIndex] = new SomeObject<JPanel>(
new JPanel(), UUID.randomUUID().toString(), r, (float)r * 33, (long)r);
} else {
//System.out.printf("sharedArray[%s]=<obj>!%n", randomIndex);
if (item.value == null ||
(item.stringField == null) ||
(item.doubleField == 0) ||
(item.floatField == 0) ||
(item.longField == 0)
) {
System.err.printf("watcher %s sees default values: %s!%n", name, item);
} else {
// fully initialized! run new construction process
double r = 1 + Math.random() * 1000;
sharedArray[randomIndex] = new SomeObject<JPanel>(
new JPanel(), UUID.randomUUID().toString(), r, (float)r * 37, (long)r);
}
}
/*try {
TimeUnit.NANOSECONDS.sleep(randomIndex);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}*/
}
}
}
static class SomeObject<V> {
V value;
String stringField;
double doubleField;
float floatField;
long longField;
SomeObject(V value, String stringField, double doubleField, float floatField, long longField) {
this.value = value;
this.stringField = stringField;
this.doubleField = doubleField;
this.floatField = floatField;
this.longField = longField;
}
@Override
public String toString() {
return "SomeObject{" +
"value=" + value == null ? "null" : "<obj>" +
", stringField='" + stringField + '\'' +
", doubleField=" + doubleField +
", floatField=" + floatField +
", longField=" + longField +
'}';
}
}
}
- 但到目前为止没有效果,我已经在不同的 2,4 和 8 核 Intel/AMD PC 上尝试过,运行了几个小时的测试 - 没有重新排序效果 - System.err.printf( “watcher %s sees ...”) - 不被调用,静态sharedArray[randomIndex]引用始终包含完全构造的值。
怎么了?如何重现这个?
While writing some java article I'm trying to reproduce re-ordering in case of unsynchronized object costruction in multithreaded environment.
The case when a heavy object is constructed w/o synchonization/volatiles/finals and other threads get access to it right after constructor call.
Here is the code I try:
public class ReorderingTest {
static SomeObject<JPanel>[] sharedArray = new SomeObject[100];
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
String name = "watcher" + i;
new Thread(new Watcher(name)).start();
System.out.printf("watcher %s started!%n", name);
}
}
static class Watcher implements Runnable {
private String name;
Watcher(String name) {
this.name = name;
}
public void run() {
while (true) {
int randomIndex = (int) (Math.random() * sharedArray.length);
SomeObject<JPanel> item = sharedArray[randomIndex];
if (item == null) {
//System.out.printf("sharedArray[%s]=null%n", randomIndex);
double r = 1 + Math.random() * 1000;
sharedArray[randomIndex] = new SomeObject<JPanel>(
new JPanel(), UUID.randomUUID().toString(), r, (float)r * 33, (long)r);
} else {
//System.out.printf("sharedArray[%s]=<obj>!%n", randomIndex);
if (item.value == null ||
(item.stringField == null) ||
(item.doubleField == 0) ||
(item.floatField == 0) ||
(item.longField == 0)
) {
System.err.printf("watcher %s sees default values: %s!%n", name, item);
} else {
// fully initialized! run new construction process
double r = 1 + Math.random() * 1000;
sharedArray[randomIndex] = new SomeObject<JPanel>(
new JPanel(), UUID.randomUUID().toString(), r, (float)r * 37, (long)r);
}
}
/*try {
TimeUnit.NANOSECONDS.sleep(randomIndex);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}*/
}
}
}
static class SomeObject<V> {
V value;
String stringField;
double doubleField;
float floatField;
long longField;
SomeObject(V value, String stringField, double doubleField, float floatField, long longField) {
this.value = value;
this.stringField = stringField;
this.doubleField = doubleField;
this.floatField = floatField;
this.longField = longField;
}
@Override
public String toString() {
return "SomeObject{" +
"value=" + value == null ? "null" : "<obj>" +
", stringField='" + stringField + '\'' +
", doubleField=" + doubleField +
", floatField=" + floatField +
", longField=" + longField +
'}';
}
}
}
-But no effect so far, I've tried on different 2,4 and 8 core Intel/AMD PCs with windows, ran test for a few hours - no reordering effect - System.err.printf("watcher %s sees ...") - is not called, static sharedArray[randomIndex] reference always contains fully constructed values.
What's wrong? How to reproduce this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这是一篇不错的文章,应该展示 x86 上的重新排序(这是一项壮举,因为 x86 内存模型几乎“安全”):
http://bartoszmilewski.wordpress.com/2008/11/05/who-ordered-memory-fences-on-an-x86/
您的示例将不会显示重新排序。编译器不会重新排序为“分配后但构造前存储对象引用”,因为构造函数可能会抛出异常,因此需要恢复引用。由于给定的保证,某些处理器可能会重新排序,但没有英特尔兼容的处理器。
Here is a nice article which should show reordering on x86 (quite a feat because the x86 memory model is pretty much "safe"):
http://bartoszmilewski.wordpress.com/2008/11/05/who-ordered-memory-fences-on-an-x86/
Your example will not show reordering. The compiler will not reorder to "store object reference after allocation but before construction" because the constructor could throw and thus the reference would need to be reverted. Some Processors may reorder this, but no intel compatible ones, due to the given guaranties.
不保证重新订购。它通常仅在 JIT 确定可能会提高性能时才会发生。
在您的情况下,您正在寻找的不是重新排序,而是一个似乎未正确初始化的对象。
您的对象很可能每次都使用新的内存位置(或至少一个不在缓存中的内存位置)在 x86/x64 架构上,我发现缓存在第一次加载内存时总是正确的由另一个线程更新。
Re-ordering is not guaranteed. It usually only occurs when the JIT determines there might be a performance gain.
In your case, what you are looking for is not re-ordering but an Object which doesn't appear to have been correctly initialised.
Your objects are highly likely to use a new memory location each time (or at least one which is not in cache) On x86/x64 architectures, I have found that the cache will always be correct the first time it has to load memory when was updated by another thread.
重新排序并不一定会发生。当它发生时,很难观察到。
100
的样本量太小了。首先尝试十亿。假设发生重新排序,在写入时,首先分配引用,然后填充字段。您的读取过程首先读取引用,然后读取字段。所以写和读遵循相同的顺序,读几乎不可能赶上并领先于写。
来解决该问题
,但可能不会发生重新排序,并且您将无法观察到它。
re-ordering does not necessarily happen. and when it does, it's hard to observe. you sample size of
100
is too tiny. Try a billion for starters.Suppose re-ordering happens, at the time of write, reference is assigned first, then fields are populated. Your read process reads the reference first, then reads the fields. So write and read follow the same order, it's almost impossible for read to catch up and get ahead of write.
You can address the issue by
Still, reordering may not occur and you won't be able to observe it.
您可以尝试将
sharedArray
声明为非易失性吗?在我看来,将易失性引用传递给正确构造的对象是安全的。could you try to declare
sharedArray
as non-volatile? It seems to me that handing a volatile reference to a correctly constructed object is safe.