如何测试线程之间的值的可见性

时间:2009-09-12 06:07:07

标签: java multithreading unit-testing testing concurrency

测试线程之间的值可见性的最佳方法是什么?

class X {
 private volatile Object ref;

 public Object getRef() {
  return ref;
 }

 public void setRef(Object newRef) {
  this.ref = newRef;
 }
}

类X公开了对ref对象的引用。如果并发线程读取并写入对象引用,则每个Thread必须查看已设置的最新对象。 volatile修饰符应该这样做。这里的实现是一个示例,它也可以是同步的或基于锁的实现。

现在我正在寻找一种方法来编写一个测试,告知我什么时候值可见性不符合规定(读取旧值)。

如果测试确实会烧掉一些cpu周期,那也没关系。

5 个答案:

答案 0 :(得分:2)

如果我理解正确,那么你想要一个测试用例,如果变量被定义为volatile而传递,如果没有则失败。

但是我认为没有可靠的方法来做到这一点。根据jvm的实现,即使没有volatile,并发访问也可以正常工作。

因此,如果指定了volatile,则单元测试 正常工作但可能可能无波动地正常工作。

答案 1 :(得分:2)

JLS说明了为了在涉及“线程间操作”的应用程序中获得有保证的一致执行,您应该做些什么。如果您不执行这些操作,则执行可能不一致。但是它实际上是否会不一致取决于您使用的JVM,您正在使用的硬件,应用程序,输入数据以及......运行应用程序时机器上可能发生的任何其他情况。

我看不出你提出的测试会告诉你什么。如果测试显示执行不一致,则会确认正确进行同步的智慧。但是如果运行测试一段时间只显示(显然)一致的执行,这并不能告诉你执行总是一致的。


示例:

假设您在(例如)JDK 1.4.2(rev 12)/ Linux / 32bit上运行测试,其中“客户端”JVM和选项x,y,z在单个处理器计算机上运行。在运行测试1000次后,如果忽略volatile,您会发现似乎没有任何区别。你在那种情况下实际学到了什么?

  • 你还没知道真的没有什么区别?如果您更改测试以使用更多线程等,您可能会得到不同的答案。如果您运行测试几千或几百万或十亿次,您可能会得到不同的答案。
  • 您还没有了解其他平台可能发生的事情;例如不同的Java版本,不同的硬件或不同的系统负载条件。
  • 您尚未了解 safe 是否遗漏了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完成的,它是如何调度指令的。这就是我要做的。使用调试器。启动一个线程来写入数据并在执行此操作的行上放置一个断点。启动第二个线程并让它读取数据,同时停止。现在,执行第一个线程来执行写入的代码,然后使用第二个线程进行读取。在您的示例中,您将无法实现此目的,因为读取和写入是单个指令。但通常如果这些操作更复杂,您可以交替执行两个线程并查看是否一切都是一致的。

这需要一些时间,因为它不是自动化的。但我不会去写一个单元测试,尝试读写很多次,希望能找到失败的情况,因为你不会做任何事情。单元测试的作用是确保您编写的代码按预期工作。但是在这种情况下,如果测试通过,你就不会确定是否有任何感觉。也许这只是幸运的,冲突并没有出现在这次运行中。这就失败了。