测试线程之间的值可见性的最佳方法是什么?
class X {
private volatile Object ref;
public Object getRef() {
return ref;
}
public void setRef(Object newRef) {
this.ref = newRef;
}
}
类X公开了对ref
对象的引用。如果并发线程读取并写入对象引用,则每个Thread必须查看已设置的最新对象。 volatile
修饰符应该这样做。这里的实现是一个示例,它也可以是同步的或基于锁的实现。
现在我正在寻找一种方法来编写一个测试,告知我什么时候值可见性不符合规定(读取旧值)。
如果测试确实会烧掉一些cpu周期,那也没关系。
答案 0 :(得分:2)
如果我理解正确,那么你想要一个测试用例,如果变量被定义为volatile而传递,如果没有则失败。
但是我认为没有可靠的方法来做到这一点。根据jvm的实现,即使没有volatile,并发访问也可以正常工作。
因此,如果指定了volatile,则单元测试 将正常工作但可能可能无波动地正常工作。
答案 1 :(得分:2)
JLS说明了为了在涉及“线程间操作”的应用程序中获得有保证的一致执行,您应该做些什么。如果您不执行这些操作,则执行可能不一致。但是它实际上是否会不一致取决于您使用的JVM,您正在使用的硬件,应用程序,输入数据以及......运行应用程序时机器上可能发生的任何其他情况。
我看不出你提出的测试会告诉你什么。如果测试显示执行不一致,则会确认正确进行同步的智慧。但是如果运行测试一段时间只显示(显然)一致的执行,这并不能告诉你执行总是一致的。
示例:
假设您在(例如)JDK 1.4.2(rev 12)/ Linux / 32bit上运行测试,其中“客户端”JVM和选项x,y,z在单个处理器计算机上运行。在运行测试1000次后,如果忽略volatile
,您会发现似乎没有任何区别。你在那种情况下实际学到了什么?
volatile
关键字。如果测试显示出差异,您只能学到一些东西。你学到的唯一一点是同步很重要 ......这就是所有教科书等一直在告诉你的事情: - )
底线:这是最糟糕的黑盒测试。它没有让你真正了解盒子里面发生了什么。要获得这种洞察力,您需要1)了解内存模型,2)深入分析JIT编译器发出的本机代码(在多个平台上......)
答案 2 :(得分:1)
class Wrapper {
private X x = new X();
private volatile Object volatileRef;
private final Object setterLock = new Object();
private final Object getterLock = new Object();
public Object getRef() {
synchronized(getterLock) {
Object refFromX = x.getRef();
if (refFromX != volatileRef) {
// FAILURE CASE!
}
return refFromX;
}
}
public void setRef(Object ref) {
synchronized(setterLock) {
volatileRef = ref;
x.setRef(ref);
}
}
}
这有用吗? 当然,你必须创建许多线程来命中这个包装器,希望出现坏的情况。
答案 3 :(得分:0)
这个怎么样?
public class XTest {
@Test
public void testRefIsVolatile() {
Field field = null;
try {
field = X.class.getDeclaredField("ref");
} catch (SecurityException e) {
e.printStackTrace();
Assert.fail(e.getMessage());
} catch (NoSuchFieldException e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
Assert.assertNotNull("Ref field", field);
Assert.assertTrue("Is Volatile", Modifier.isVolatile(field
.getModifiers()));
}
}
答案 4 :(得分:0)
所以基本上你想要这个场景:一个线程写入变量,而另一个线程同时读取它,并且你想确保变量read具有正确的值,对吗?
嗯,我认为你不能使用单元测试,因为你无法确保合适的环境。这是由JVM完成的,它是如何调度指令的。这就是我要做的。使用调试器。启动一个线程来写入数据并在执行此操作的行上放置一个断点。启动第二个线程并让它读取数据,同时停止。现在,执行第一个线程来执行写入的代码,然后使用第二个线程进行读取。在您的示例中,您将无法实现此目的,因为读取和写入是单个指令。但通常如果这些操作更复杂,您可以交替执行两个线程并查看是否一切都是一致的。
这需要一些时间,因为它不是自动化的。但我不会去写一个单元测试,尝试读写很多次,希望能找到失败的情况,因为你不会做任何事情。单元测试的作用是确保您编写的代码按预期工作。但是在这种情况下,如果测试通过,你就不会确定是否有任何感觉。也许这只是幸运的,冲突并没有出现在这次运行中。这就失败了。